@jinntec/fore 2.9.0 → 3.0.1

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.
@@ -82,17 +82,20 @@ export default class FxSetvalue extends AbstractAction {
82
82
  */
83
83
  dispatchExecute() {}
84
84
 
85
+ // Adjustment in setValue logic to ensure we work with JSONNode, not just raw values
85
86
  setValue(modelItem, newVal) {
86
87
  console.log('setValue', modelItem, newVal);
87
88
  const item = modelItem;
88
89
  if (!item) return;
89
90
 
90
- if (item.value !== newVal) {
91
- // const path = XPathUtil.getPath(modelItem.node);
92
- const path = Fore.getDomNodeIndexString(modelItem.node);
91
+ // Check if current node is a JSONNode
92
+ const node = Array.isArray(item.node) ? item.node[0] : item.node;
93
93
 
94
+ if (item.value !== newVal) {
95
+ const path = Fore.getDomNodeIndexString(node);
94
96
  const ev = this.event;
95
97
  const targetElem = this;
98
+
96
99
  this.dispatchEvent(
97
100
  new CustomEvent('execute-action', {
98
101
  composed: true,
@@ -107,20 +110,19 @@ export default class FxSetvalue extends AbstractAction {
107
110
  }),
108
111
  );
109
112
 
113
+ // Use ModelItem's value setter which handles both DOM nodes and JSON lenses
110
114
  if (newVal?.nodeType) {
111
115
  if (newVal.nodeType === Node.ELEMENT_NODE) {
112
116
  item.value = newVal;
113
- }
114
- if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
115
- item.value = newVal.getValue();
116
- }
117
- if (newVal.nodeType === Node.TEXT_NODE) {
117
+ } else if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
118
+ item.value = newVal.nodeValue;
119
+ } else if (newVal.nodeType === Node.TEXT_NODE) {
118
120
  item.value = newVal.textContent;
119
121
  }
120
122
  } else {
121
123
  item.value = newVal;
122
- item.node.textContent = newVal;
123
124
  }
125
+
124
126
  this.getModel().changed.push(modelItem);
125
127
  this.needsUpdate = true;
126
128
  modelItem.notify();
package/src/fore.js CHANGED
@@ -293,82 +293,39 @@ export class Fore {
293
293
  * @returns {Promise<void>}
294
294
  */
295
295
  static async refreshChildren(startElement, force) {
296
- const refreshed = new Promise(resolve => {
297
- /*
298
- if there's an 'refresh-on-view' attribute the element wants to be handled by
299
- handleIntersect function that calls the refresh of the respective element and
300
- not the global one.
301
- */
302
- // if(!force && startElement.hasAttribute('refresh-on-view')) return;
303
-
304
- /* ### attempt with querySelectorAll is even slower than iterating recursively
305
-
306
- const children = startElement.querySelectorAll('[ref]');
307
- Array.from(children).forEach(uiElement => {
308
- if (Fore.isUiElement(uiElement.nodeName) && typeof uiElement.refresh === 'function') {
309
- uiElement.refresh();
310
- }
311
- });
312
- */
313
- const { children } = startElement;
314
- if (children) {
315
- for (const element of Array.from(children)) {
316
- if (element.nodeName.toUpperCase() === 'FX-FORE') {
317
- break;
318
- }
319
- if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
320
- /**
321
- * @type {import('./ForeElementMixin.js').default}
322
- */
323
- const bound = element;
324
- if (!force) {
325
- continue;
326
- }
327
- /*
328
- if(element.nodeName === 'FX-CASE') {
329
- console.log('hey - got a case', element);
330
- }
331
- */
332
- if (force === true) {
333
- // console.log('🔄 refreshing ', element);
334
- // Unconditional force refresh
335
- bound.refresh(force);
336
- continue;
337
- }
338
- if (typeof force !== 'object') {
339
- continue;
340
- }
341
- /*
342
- if (
343
- force.reason === 'index-function' &&
344
- bound.dependencies.isInvalidatedByIndexFunction()
345
- ) {
346
- console.log('🔄 refreshing ', element);
347
-
348
- bound.refresh(force);
349
- continue;
350
- }
351
-
352
- if (
353
- bound.dependencies.isInvalidatedByChildlistChanges(force.elementLocalnamesWithChanges)
354
- ) {
355
- console.log('🔄 refreshing ', element);
356
-
357
- bound.refresh(force);
358
- continue;
359
- }
360
- */
296
+ const children = startElement?.children ? Array.from(startElement.children) : [];
297
+ for (const element of children) {
298
+ // Do not cross into nested fore roots
299
+ if (element.nodeName.toUpperCase() === 'FX-FORE') {
300
+ break;
301
+ }
302
+
303
+ if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
304
+ /** @type {import('./ForeElementMixin.js').default} */
305
+ const bound = element;
306
+
307
+ // Keep old behavior: only refresh UI elements during full/forced refresh
308
+ if (!force) {
309
+ // still recurse below
310
+ } else if (force === true) {
311
+ const maybePromise = bound.refresh(force);
312
+ if (maybePromise && typeof maybePromise.then === 'function') {
313
+ await maybePromise;
361
314
  }
362
- if (!(element.inert === true)) {
363
- // testing for inert catches model and action elements and should just leave updateable html elements
364
- Fore.refreshChildren(element, force);
315
+ } else if (typeof force === 'object') {
316
+ // future selective refresh logic can live here if you re-enable it
317
+ const maybePromise = bound.refresh(force);
318
+ if (maybePromise && typeof maybePromise.then === 'function') {
319
+ await maybePromise;
365
320
  }
366
321
  }
367
322
  }
368
- resolve('done');
369
- });
370
323
 
371
- return refreshed;
324
+ // Traverse DOM unless inert
325
+ if (!(element.inert === true)) {
326
+ await Fore.refreshChildren(element, force);
327
+ }
328
+ }
372
329
  }
373
330
 
374
331
  static copyDom(inputElement) {
@@ -1,8 +1,27 @@
1
1
  import { createTypedValueFactory, registerCustomXPathFunction } from 'fontoxpath';
2
2
  import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation.js';
3
3
 
4
+ // FontoXPath custom functions are registered globally. If multiple <fx-functionlib> (or multiple
5
+ // <fx-fore> instances) register the same function name+arity, later registrations would overwrite
6
+ // earlier ones across the whole page. We enforce "first wins".
7
+ const _registeredFunctionKeys = new Set();
8
+
9
+ function _makeFunctionKey(functionIdentifier, arity) {
10
+ if (typeof functionIdentifier === 'string') {
11
+ return `str:${functionIdentifier}#${arity}`;
12
+ }
13
+ return `{${functionIdentifier.namespaceURI}}${functionIdentifier.localName}#${arity}`;
14
+ }
15
+
16
+ function _ensureGlobalUnprefixedName(localName) {
17
+ // Keep this list unique to avoid unbounded growth across tests
18
+ if (!globallyDeclaredFunctionLocalNames.includes(localName)) {
19
+ globallyDeclaredFunctionLocalNames.push(localName);
20
+ }
21
+ }
22
+
4
23
  /**
5
- * @param functionObject {{signature: string, type: string|null, functionBody: string}}
24
+ * @param functionObject {{signature: string, type: string|null, functionBody: string, implementation?: Function}}
6
25
  * @param formElement {HTMLElement} The form element connected to this function. Used to determine inscope context
7
26
  * @returns {undefined}
8
27
  */
@@ -31,29 +50,51 @@ export default function registerFunction(functionObject, formElement) {
31
50
  ? { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName }
32
51
  : `${prefix}:${localName}`;
33
52
 
34
- // Make the function available globally w/o a prefix. See the functionNameResolver for for how
35
- // functionObject is picked up
36
- if (!prefix) {
37
- globallyDeclaredFunctionLocalNames.push(localName);
38
- }
39
-
40
53
  const paramParts = params
41
- ? params.split(',').map(param => {
42
- const match = param.match(/(?<variableName>\$[^\s]+)(?:\sas\s(?<varType>[^\s]+))/);
43
- if (!match) {
44
- throw new Error(`Param ${param} could not be parsed`);
45
- }
46
- const { variableName, varType } = match.groups;
47
- return {
48
- variableName,
49
- variableType: varType || 'item()*',
50
- };
51
- })
54
+ ? params
55
+ .split(',')
56
+ .map(param => param.trim())
57
+ .filter(Boolean)
58
+ .map(param => {
59
+ const match = param.match(/(?<variableName>\$[^\s]+)(?:\sas\s(?<varType>[^\s]+))/);
60
+ if (!match) {
61
+ throw new Error(`Param ${param} could not be parsed`);
62
+ }
63
+ const { variableName, varType } = match.groups;
64
+ return {
65
+ variableName,
66
+ variableType: varType || 'item()*',
67
+ };
68
+ })
52
69
  : [];
53
70
 
71
+ const arity = paramParts.length;
72
+
73
+ // -------------------------------------------------
74
+ // FIRST-WINS GUARD (name + arity) WITH NAME EXPORT
75
+ // -------------------------------------------------
76
+ const key = _makeFunctionKey(functionIdentifier, arity);
77
+
78
+ if (_registeredFunctionKeys.has(key)) {
79
+ // If this registration is unprefixed, we must still make it callable without prefix.
80
+ // This fixes the unit test case where local:hello-world() was registered earlier and
81
+ // hello-world() should resolve to that implementation.
82
+ if (!prefix) {
83
+ _ensureGlobalUnprefixedName(localName);
84
+ }
85
+ return;
86
+ }
87
+
88
+ _registeredFunctionKeys.add(key);
89
+
90
+ // Make the function available globally w/o a prefix.
91
+ if (!prefix) {
92
+ _ensureGlobalUnprefixedName(localName);
93
+ }
94
+
54
95
  switch (type) {
55
96
  case 'text/javascript': {
56
- // NEW: if a real JS function is provided (module libs), register it directly.
97
+ // If a real JS function is provided (module libs), register it directly.
57
98
  if (typeof functionObject.implementation === 'function') {
58
99
  const impl = functionObject.implementation;
59
100
  registerCustomXPathFunction(
@@ -74,6 +115,7 @@ export default function registerFunction(functionObject, formElement) {
74
115
  'form',
75
116
  functionObject.functionBody,
76
117
  );
118
+
77
119
  registerCustomXPathFunction(
78
120
  functionIdentifier,
79
121
  paramParts.map(paramPart => paramPart.variableType),
@@ -83,25 +125,27 @@ export default function registerFunction(functionObject, formElement) {
83
125
  );
84
126
  break;
85
127
  }
128
+
86
129
  case 'text/xquf':
87
130
  case 'text/xquery':
88
131
  case 'text/xpath': {
89
132
  const typedValueFactories = paramParts.map(param =>
90
133
  createTypedValueFactory(param.variableType),
91
134
  );
135
+
92
136
  const language =
93
137
  type === 'text/xpath'
94
138
  ? 'XPath3.1'
95
139
  : type === 'text/xquery'
96
140
  ? 'XQuery3.1'
97
141
  : 'XQueryUpdate3.1';
142
+
98
143
  const fun = (domFacade, ...args) =>
99
144
  evaluateXPath(
100
145
  functionObject.functionBody,
101
146
  formElement.getInScopeContext(),
102
147
  formElement.getOwnerForm(),
103
148
  paramParts.reduce((variablesByName, paramPart, i) => {
104
- // Because we know the XPath type here (from the function declaration) we do not have to depend on the implicit typings
105
149
  variablesByName[paramPart.variableName.replace('$', '')] = typedValueFactories[i](
106
150
  args[i],
107
151
  domFacade,
@@ -110,6 +154,7 @@ export default function registerFunction(functionObject, formElement) {
110
154
  }, {}),
111
155
  { language },
112
156
  );
157
+
113
158
  registerCustomXPathFunction(
114
159
  functionIdentifier,
115
160
  paramParts.map(paramPart => paramPart.variableType),
package/src/fx-bind.js CHANGED
@@ -9,7 +9,6 @@ import {
9
9
  import { XPathUtil } from './xpath-util.js';
10
10
  import getInScopeContext from './getInScopeContext.js';
11
11
  import { getPath } from './xpath-path.js';
12
- import { evaluateXPathToFirstNode } from 'fontoxpath';
13
12
 
14
13
  /**
15
14
  * FxBind declaratively attaches constraints to nodes in the data (instances).
@@ -157,86 +156,75 @@ export class FxBind extends ForeElementMixin {
157
156
  */
158
157
  init(model) {
159
158
  this.model = model;
160
- // console.log('init binding ', this);
161
159
  this._getInstanceId();
162
160
  this.bindType = this.getModel().getInstance(this.instanceId).type;
163
- // console.log('binding type ', this.bindType);
164
161
 
162
+ // ✅ Always evaluate nodeset first (XML + JSON)
163
+ this._evalInContext();
164
+
165
+ // ✅ Build dependency graph for both types
166
+ this._buildBindGraph();
167
+
168
+ // ✅ Create modelitems for both types
165
169
  if (this.bindType === 'xml') {
166
- this._evalInContext();
167
- this._buildBindGraph();
168
170
  this._createModelItems();
171
+ } else if (this.bindType === 'json') {
172
+ this._createModelItemsForJSON();
169
173
  }
170
- // todo: support json
171
174
 
172
- // ### process child bindings
173
175
  this._processChildren(model);
174
176
  }
175
177
 
176
178
  _buildBindGraph() {
177
- if (this.bindType === 'xml') {
178
- this.nodeset.forEach(node => {
179
- const instance = XPathUtil.resolveInstance(this, this.ref);
180
-
181
- const path = getPath(node, instance);
182
- this.model.mainGraph.addNode(path, node);
183
-
184
- /* ### catching references in the 'ref' itself...
185
- todo: investigate cases where 'ref' attributes use predicates pointing to other nodes. These would not be handled
186
- in current implementation.
187
-
188
- General question: are there valid use-cases for using a 'filter' expression to narrow the nodeset
189
- where to apply constraints? Guess yes and if it's 'just' for reducing the amount of necessary modelItem objects.
179
+ // ✅ Works for XML and JSON (JSON nodes have getPath()/getPath() handles __jsonlens__)
180
+ this.nodeset.forEach(node => {
181
+ const instanceId = XPathUtil.resolveInstance(this, this.ref);
182
+ const path = getPath(node, instanceId);
190
183
 
191
- */
192
- // const foreignRefs = this.getReferences(this.ref);
184
+ this.model.mainGraph.addNode(path, node);
193
185
 
194
- if (this.calculate) {
195
- this.model.mainGraph.addNode(`${path}:calculate`, node);
196
- // Calculated values are a dependency of the model item.
197
- this.model.mainGraph.addDependency(path, `${path}:calculate`);
198
- }
186
+ if (this.calculate) {
187
+ this.model.mainGraph.addNode(`${path}:calculate`, node);
188
+ this.model.mainGraph.addDependency(path, `${path}:calculate`);
189
+ }
199
190
 
200
- const calculateRefs = this._getReferencesForProperty(this.calculate, node);
201
- if (calculateRefs.length !== 0) {
202
- this._addDependencies(calculateRefs, node, path, 'calculate');
203
- }
191
+ const calculateRefs = this._getReferencesForProperty(this.calculate, node);
192
+ if (calculateRefs.length !== 0) {
193
+ this._addDependencies(calculateRefs, node, path, 'calculate', instanceId);
194
+ }
204
195
 
205
- if (!this.calculate) {
206
- const readonlyRefs = this._getReferencesForProperty(this.readonly, node);
207
- if (readonlyRefs.length !== 0) {
208
- this._addDependencies(readonlyRefs, node, path, 'readonly');
209
- } else if (this.readonly) {
210
- this.model.mainGraph.addNode(`${path}:readonly`, node);
211
- }
196
+ if (!this.calculate) {
197
+ const readonlyRefs = this._getReferencesForProperty(this.readonly, node);
198
+ if (readonlyRefs.length !== 0) {
199
+ this._addDependencies(readonlyRefs, node, path, 'readonly', instanceId);
200
+ } else if (this.readonly) {
201
+ this.model.mainGraph.addNode(`${path}:readonly`, node);
212
202
  }
203
+ }
213
204
 
214
- // const requiredRefs = this.requiredReferences;
215
- const requiredRefs = this._getReferencesForProperty(this.required, node);
216
- if (requiredRefs.length !== 0) {
217
- this._addDependencies(requiredRefs, node, path, 'required');
218
- } else if (this.required) {
219
- this.model.mainGraph.addNode(`${path}:required`, node);
220
- }
205
+ const requiredRefs = this._getReferencesForProperty(this.required, node);
206
+ if (requiredRefs.length !== 0) {
207
+ this._addDependencies(requiredRefs, node, path, 'required', instanceId);
208
+ } else if (this.required) {
209
+ this.model.mainGraph.addNode(`${path}:required`, node);
210
+ }
221
211
 
222
- const relevantRefs = this._getReferencesForProperty(this.relevant, node);
223
- if (relevantRefs.length !== 0) {
224
- this._addDependencies(relevantRefs, node, path, 'relevant');
225
- } else if (this.relevant) {
226
- this.model.mainGraph.addNode(`${path}:relevant`, node);
227
- }
212
+ const relevantRefs = this._getReferencesForProperty(this.relevant, node);
213
+ if (relevantRefs.length !== 0) {
214
+ this._addDependencies(relevantRefs, node, path, 'relevant', instanceId);
215
+ } else if (this.relevant) {
216
+ this.model.mainGraph.addNode(`${path}:relevant`, node);
217
+ }
228
218
 
229
- const constraintRefs = this._getReferencesForProperty(this.constraint, node);
230
- if (constraintRefs.length !== 0) {
231
- this._addDependencies(constraintRefs, node, path, 'constraint');
232
- } else if (this.constraint) {
233
- this.model.mainGraph.addNode(`${path}:constraint`, node);
234
- this.model.mainGraph.addDependency(path, `${path}:constraint`);
235
- }
236
- });
237
- }
219
+ const constraintRefs = this._getReferencesForProperty(this.constraint, node);
220
+ if (constraintRefs.length !== 0) {
221
+ this._addDependencies(constraintRefs, node, path, 'constraint', instanceId);
222
+ } else if (this.constraint) {
223
+ this.model.mainGraph.addNode(`${path}:constraint`, node);
224
+ this.model.mainGraph.addDependency(path, `${path}:constraint`);
225
+ }
226
+ });
238
227
  }
239
-
240
228
  /**
241
229
  * Resolves a referenced ModelItem using the model's graph and node registry.
242
230
  * @param {string} refName
@@ -266,26 +254,25 @@ export class FxBind extends ForeElementMixin {
266
254
  * @param {string} path The path to the start of the reference
267
255
  * @param {string} property The property with this dependency
268
256
  */
269
- _addDependencies(refs, node, path, property) {
270
- // console.log('_addDependencies',path);
257
+ _addDependencies(refs, node, path, property, instanceId) {
271
258
  const nodeHash = `${path}:${property}`;
259
+
272
260
  if (refs.length !== 0) {
273
261
  if (!this.model.mainGraph.hasNode(nodeHash)) {
274
262
  this.model.mainGraph.addNode(nodeHash, node);
275
263
  }
264
+
276
265
  refs.forEach(ref => {
277
- const instance = XPathUtil.resolveInstance(this, path);
266
+ const otherPath = getPath(ref, instanceId);
278
267
 
279
- const otherPath = getPath(ref, instance);
280
- // console.log('otherPath', otherPath)
268
+ // keep old XML-only hack
269
+ if (this.bindType === 'xml' && otherPath.endsWith('text()[1]')) return;
281
270
 
282
- // todo: nasty hack to prevent duplicate pathes like 'a[1]' and 'a[1]/text()[1]' to end up as separate nodes in the graph
283
- if (!otherPath.endsWith('text()[1]')) {
284
- if (!this.model.mainGraph.hasNode(otherPath)) {
285
- this.model.mainGraph.addNode(otherPath, ref);
286
- }
287
- this.model.mainGraph.addDependency(nodeHash, otherPath);
271
+ if (!this.model.mainGraph.hasNode(otherPath)) {
272
+ this.model.mainGraph.addNode(otherPath, ref);
288
273
  }
274
+
275
+ this.model.mainGraph.addDependency(nodeHash, otherPath);
289
276
  });
290
277
  } else {
291
278
  this.model.mainGraph.addNode(nodeHash, node);
@@ -311,6 +298,23 @@ export class FxBind extends ForeElementMixin {
311
298
  return null;
312
299
  }
313
300
 
301
+ _createModelItemsForJSON() {
302
+ const fore = this.closest('fx-fore');
303
+ const instanceId = this.instanceId;
304
+
305
+ this.nodeset.forEach(jsonNode => {
306
+ const path = getPath(jsonNode, instanceId);
307
+
308
+ // ✅ ModelItem node should be the JSONNode itself (lens), NOT JSONLens
309
+ const newItem = new ModelItem(path, this.getBindingExpr(), jsonNode, this, instanceId, fore);
310
+
311
+ const alert = this.getAlert();
312
+ if (alert) newItem.addAlert(alert);
313
+
314
+ this.getModel().registerModelItem(newItem);
315
+ });
316
+ }
317
+
314
318
  /**
315
319
  * overwrites
316
320
  */
@@ -346,8 +350,11 @@ export class FxBind extends ForeElementMixin {
346
350
  const inst = this.getModel().getInstance(this.instanceId);
347
351
  if (inst.type === 'xml') {
348
352
  this.nodeset = evaluateXPathToNodes(this.ref, inscopeContext, this);
353
+ } else if (inst.type === 'json') {
354
+ // ✅ JSON must also resolve the nodeset via XPath evaluation
355
+ this.nodeset = evaluateXPathToNodes(this.ref, inscopeContext, this);
349
356
  } else {
350
- this.nodeset = this.ref;
357
+ this.nodeset = [];
351
358
  }
352
359
  }
353
360
  }
@@ -458,6 +465,33 @@ export class FxBind extends ForeElementMixin {
458
465
  }
459
466
 
460
467
  getReferences(propertyExpr) {
468
+ // For XML, DependencyNotifyingDomFacade reliably reports the nodes touched during evaluation.
469
+ // For JSON lens nodes, the domFacade hook does not fire (evaluation goes through our lens resolver),
470
+ // so we must extract lookup tokens and resolve them explicitly.
471
+
472
+ if (!propertyExpr) return [];
473
+
474
+ // JSON path: resolve dependencies by parsing lens lookups in the expression.
475
+ if (this.bindType === 'json') {
476
+ const touchedNodes = new Set();
477
+ const tokens = this._extractJsonLookupTokens(propertyExpr);
478
+
479
+ // Evaluate each token in the *current* context node (each item in nodeset)
480
+ this.nodeset.forEach(node => {
481
+ tokens.forEach(token => {
482
+ try {
483
+ const refs = evaluateXPathToNodes(token, node, this);
484
+ refs.forEach(r => touchedNodes.add(r));
485
+ } catch (_e) {
486
+ // ignore: dependency extraction must never break bind initialization
487
+ }
488
+ });
489
+ });
490
+
491
+ return Array.from(touchedNodes.values());
492
+ }
493
+
494
+ // XML path: use dom facade for accurate dependency tracking
461
495
  const touchedNodes = new Set();
462
496
  const domFacade = new DependencyNotifyingDomFacade(otherNode => touchedNodes.add(otherNode));
463
497
  this.nodeset.forEach(node => {
@@ -465,6 +499,27 @@ export class FxBind extends ForeElementMixin {
465
499
  });
466
500
  return Array.from(touchedNodes.values());
467
501
  }
502
+ _extractJsonLookupTokens(expr) {
503
+ if (!expr) return [];
504
+
505
+ const src = String(expr);
506
+ const tokens = new Set();
507
+
508
+ // instance('id')?a?b?c or instance('id')?*
509
+ const instRe = /instance\s*\([^)]*\)\s*(?:\?\s*\*|\?\s*[a-zA-Z_][\w-]*)+/g;
510
+ let m;
511
+ while ((m = instRe.exec(src)) !== null) {
512
+ if (m[0]) tokens.add(m[0].replace(/\s+/g, ''));
513
+ }
514
+
515
+ // relative lookups like ?title, ?year, ?ui, ?query (ignore ?*)
516
+ const relRe = /\?[a-zA-Z_][\w-]*/g;
517
+ while ((m = relRe.exec(src)) !== null) {
518
+ if (m[0] && m[0] !== '?*') tokens.add(m[0]);
519
+ }
520
+
521
+ return Array.from(tokens);
522
+ }
468
523
 
469
524
  /*
470
525
  static getReferencesForRef(ref,nodeset){