@jinntec/fore 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/fore-dev.js +2 -2
  3. package/dist/fore-dev.js.map +1 -1
  4. package/dist/fore.js +2 -2
  5. package/dist/fore.js.map +1 -1
  6. package/index.js +4 -2
  7. package/package.json +10 -38
  8. package/resources/fore.css +263 -262
  9. package/resources/vars.css +8 -0
  10. package/src/DependencyNotifyingDomFacade.js +1 -0
  11. package/src/ForeElementMixin.js +243 -228
  12. package/src/actions/abstract-action.js +407 -398
  13. package/src/actions/fx-action.js +6 -6
  14. package/src/actions/fx-append.js +1 -1
  15. package/src/actions/fx-call.js +63 -62
  16. package/src/actions/fx-confirm.js +11 -11
  17. package/src/actions/fx-construct-done.js +4 -4
  18. package/src/actions/fx-copy.js +36 -36
  19. package/src/actions/fx-delete.js +77 -79
  20. package/src/actions/fx-dispatch.js +14 -14
  21. package/src/actions/fx-hide.js +15 -15
  22. package/src/actions/fx-insert.js +9 -12
  23. package/src/actions/fx-insertchild.js +88 -0
  24. package/src/actions/fx-load.js +188 -185
  25. package/src/actions/fx-message.js +18 -19
  26. package/src/actions/fx-refresh.js +10 -10
  27. package/src/actions/fx-reload.js +16 -14
  28. package/src/actions/fx-replace.js +0 -1
  29. package/src/actions/fx-reset.js +31 -31
  30. package/src/actions/fx-send.js +59 -52
  31. package/src/actions/fx-setattribute.js +48 -49
  32. package/src/actions/fx-setfocus.js +54 -58
  33. package/src/actions/fx-setvalue.js +85 -81
  34. package/src/actions/fx-show.js +11 -11
  35. package/src/actions/fx-toggle.js +2 -2
  36. package/src/actions/fx-toggleboolean.js +38 -39
  37. package/src/actions/fx-unmodified.js +27 -0
  38. package/src/actions/fx-update.js +6 -6
  39. package/src/dep_graph.js +1 -1
  40. package/src/drawdown.js +23 -30
  41. package/src/events.js +19 -19
  42. package/src/fore.js +143 -125
  43. package/src/functions/common-function.js +24 -25
  44. package/src/functions/fx-function.js +6 -99
  45. package/src/functions/fx-functionlib.js +56 -0
  46. package/src/functions/registerFunction.js +112 -0
  47. package/src/fx-bind.js +19 -24
  48. package/src/fx-connection.js +226 -0
  49. package/src/fx-fore.js +844 -815
  50. package/src/fx-header.js +4 -4
  51. package/src/fx-instance.js +25 -29
  52. package/src/fx-model.js +405 -380
  53. package/src/fx-submission.js +399 -397
  54. package/src/fx-var.js +13 -12
  55. package/src/getInScopeContext.js +102 -89
  56. package/src/json-util.js +24 -24
  57. package/src/lab/fore-component.js +48 -52
  58. package/src/lab/instance-inspector.js +13 -16
  59. package/src/modelitem.js +48 -10
  60. package/src/relevance.js +12 -16
  61. package/src/tools/adi.js +858 -812
  62. package/src/tools/fx-action-log.js +377 -295
  63. package/src/tools/fx-devtools.js +210 -210
  64. package/src/tools/fx-dom-inspector.js +123 -122
  65. package/src/tools/fx-json-instance.js +262 -253
  66. package/src/tools/fx-log-item.js +6 -11
  67. package/src/tools/fx-log-settings.js +358 -301
  68. package/src/tools/fx-minimap.js +186 -177
  69. package/src/tools/helpers.js +1 -1
  70. package/src/ui/abstract-control.js +73 -53
  71. package/src/ui/fx-case.js +59 -7
  72. package/src/ui/fx-container.js +13 -14
  73. package/src/ui/fx-control.js +572 -538
  74. package/src/ui/fx-dialog.js +3 -3
  75. package/src/ui/fx-droptarget.js +3 -4
  76. package/src/ui/fx-group.js +4 -2
  77. package/src/ui/fx-hint.js +3 -0
  78. package/src/ui/fx-inspector.js +5 -5
  79. package/src/ui/fx-items.js +11 -11
  80. package/src/ui/fx-output.js +10 -12
  81. package/src/ui/fx-repeat-attributes.js +23 -27
  82. package/src/ui/fx-repeat.js +347 -344
  83. package/src/ui/fx-repeatitem.js +75 -68
  84. package/src/ui/fx-switch.js +39 -14
  85. package/src/ui/fx-trigger.js +4 -4
  86. package/src/withDraggability.js +193 -191
  87. package/src/xpath-evaluation.js +969 -958
  88. package/src/xpath-util.js +161 -134
package/src/fore.js CHANGED
@@ -1,6 +1,11 @@
1
- import getInScopeContext from "./getInScopeContext.js";
2
- import {evaluateXPath, evaluateXPathToFirstNode, evaluateXPathToString, evaluateXPathToNodes} from "./xpath-evaluation.js";
3
- import { XPathUtil } from "./xpath-util.js";
1
+ import getInScopeContext from './getInScopeContext.js';
2
+ import {
3
+ evaluateXPath,
4
+ evaluateXPathToFirstNode,
5
+ evaluateXPathToString,
6
+ evaluateXPathToNodes,
7
+ } from './xpath-evaluation.js';
8
+ import { XPathUtil } from './xpath-util.js';
4
9
 
5
10
  /**
6
11
  * Class hosting common utility functions used throughout all fore elements
@@ -18,34 +23,33 @@ export class Fore {
18
23
 
19
24
  static TYPE_DEFAULT = 'xs:string';
20
25
 
21
- /**
26
+ /**
22
27
  * Loads and return a piece of HTML
23
- * @param url - the Url to load from
28
+ * @param url {String} - the Url to load from
24
29
  * @returns {Promise<string>}
25
30
  */
26
- static async loadHtml(url){
27
- try{
28
- const response = await fetch(url, {
31
+ static async loadHtml(url) {
32
+ try {
33
+ const response = await fetch(url, {
29
34
  method: 'GET',
30
35
  mode: 'cors',
31
36
  credentials: 'same-origin',
32
37
  headers: {
33
- 'Content-Type': "text/html",
38
+ 'Content-Type': 'text/html',
34
39
  },
35
40
  });
36
41
  const responseContentType = response.headers.get('content-type').toLowerCase();
37
42
  if (responseContentType.startsWith('text/html')) {
38
43
  return response.text();
39
- } else {
40
- Fore.dispatch(this, 'error', {
41
- message: `Response has wrong contentType '${responseContentType}'. Should be 'text/html'`,
42
- level:'Error'
43
- });
44
44
  }
45
- } catch (e){
45
+ Fore.dispatch(this, 'error', {
46
+ message: `Response has wrong contentType '${responseContentType}'. Should be 'text/html'`,
47
+ level: 'Error',
48
+ });
49
+ } catch (error) {
46
50
  Fore.dispatch(this, 'error', {
47
51
  message: `Html couldn't be loaded from '${url}'`,
48
- level:'Error'
52
+ level: 'Error',
49
53
  });
50
54
  }
51
55
  }
@@ -54,75 +58,80 @@ export class Fore {
54
58
  * loads a Fore element from given `src`. Always returns the first occurrence of a `<fx-fore>`. The retured element
55
59
  * will replace the `replace` element in the DOM.
56
60
  *
57
- * @param replace the element with a `src` attribute to resolvé.
58
- * @param src the Url to resolve
59
- * @param selector a querySelector expression to fetch certain element from loaded document
60
- * @returns {Promise<void>}
61
+ * @param {Element} replace the element with a `src` attribute to resolvé.
62
+ * @param {string} src the Url to resolve
63
+ * @param {string} selector a querySelector expression to fetch certain element from loaded document
64
+ * @returns {Promise<HTMLElement>} The replacement element
61
65
  */
62
- static async loadForeFromSrc(replace, src, selector){
63
- if(!src){
66
+ static async loadForeFromSrc(replace, src, selector) {
67
+ if (!src) {
64
68
  Fore.dispatch(this, 'error', {
65
69
  detail: {
66
- message: `No 'src' attribute present`,
70
+ message: "No 'src' attribute present",
67
71
  },
68
72
  });
73
+ return null;
69
74
  }
70
- await Fore.loadHtml(src)
71
- .then(data => {
72
- const parsed = new DOMParser().parseFromString(data, 'text/html');
73
- // const theFore = parsed.querySelector('fx-fore');
74
- const foreElement = parsed.querySelector(selector);
75
- // console.log('foreElement', foreElement)
76
- if (!foreElement) {
77
- Fore.dispatch(this, 'error', {
78
- detail: {
79
- message: `Fore element not found in '${src}'. Maybe wrapped within 'template' element?`,
80
- },
81
- });
82
- }
83
- foreElement.setAttribute('from-src', src);
84
- const thisAttrs = replace.attributes;
85
- Array.from(thisAttrs).forEach(attr =>{
86
- if(attr.name !== 'src'){
87
- foreElement.setAttribute(attr.name,attr.value);
88
- }
89
- });
90
- replace.replaceWith(foreElement);
91
- return foreElement;
92
- });
75
+ const data = await Fore.loadHtml(src);
76
+ const parsed = new DOMParser().parseFromString(data, 'text/html');
77
+ // const theFore = parsed.querySelector('fx-fore');
78
+ const foreElement = parsed.querySelector(selector);
79
+ // console.log('foreElement', foreElement)
80
+ if (!foreElement) {
81
+ Fore.dispatch(this, 'error', {
82
+ detail: {
83
+ message: `Fore element not found in '${src}'. Maybe wrapped within 'template' element?`,
84
+ },
85
+ });
86
+ return;
87
+ }
88
+ foreElement.setAttribute('from-src', src);
89
+ const thisAttrs = replace.attributes;
90
+ Array.from(thisAttrs).forEach(attr => {
91
+ if (attr.name !== 'src') {
92
+ foreElement.setAttribute(attr.name, attr.value);
93
+ }
94
+ });
95
+ replace.replaceWith(foreElement);
96
+ return foreElement;
93
97
  }
94
- static buildPredicates(node){
95
- let attrPredicate='';
96
- Array.from(node.attributes).forEach(attr =>{
98
+
99
+ static buildPredicates(node) {
100
+ let attrPredicate = '';
101
+ Array.from(node.attributes).forEach(attr => {
97
102
  // attrMap.set(attr.nodeName,attr.nodeValue);
98
103
  // if(attr.nodeName !== 'xmlns'){
99
104
  // if(attr.nodeValue !== ''){
100
105
  // attrPredicate += `[@${attr.nodeName}='${attr.nodeValue}']`;
101
106
  // }else{
102
- attrPredicate += `[@${attr.nodeName}]`;
103
- // }
107
+ attrPredicate += `[@${attr.nodeName}]`;
108
+ // }
104
109
  // }
105
110
  });
106
- return attrPredicate
111
+ return attrPredicate;
107
112
  }
108
113
 
109
-
110
114
  /**
111
115
  * returns true if target element is the widget itself or some element within the widget.
112
- * @param target an event target
116
+ * @param {HTMLElement} target an event target
113
117
  * @returns {boolean}
114
118
  */
115
119
  static isWidget(target) {
116
- if(target?.classList.contains("widget")) return true;
120
+ if (target?.classList.contains('widget')) return true;
117
121
  let parent = target.parentNode;
118
- while(parent && parent.nodeName !== 'FX-CONTROL'){
119
- if(parent?.classList?.contains('widget')) return true;
122
+ while (parent && parent.nodeName !== 'FX-CONTROL') {
123
+ if (parent?.classList?.contains('widget')) return true;
120
124
  parent = parent.parentNode;
121
125
  }
122
126
  return false;
123
127
  }
124
128
 
125
-
129
+ /**
130
+ * Get a string that can be used as a path to a node
131
+ *
132
+ * @param {Node} node
133
+ * @returns {string}
134
+ */
126
135
  static getDomNodeIndexString(node) {
127
136
  const indexes = [];
128
137
  let currentNode = node;
@@ -139,9 +148,14 @@ export class Fore {
139
148
  return indexes.join('.');
140
149
  }
141
150
 
142
- static getExpression(input){
143
- if(input.startsWith('{') && input.endsWith('}')){
144
- return input.substring(1, input.length - 1);
151
+ /**
152
+ * Get the expression part of something
153
+ * @param {string} input
154
+ * @returns {string}
155
+ */
156
+ static getExpression(input) {
157
+ if (input.startsWith('{') && input.endsWith('}')) {
158
+ return input.substring(1, input.length - 1);
145
159
  }
146
160
  return input;
147
161
  }
@@ -149,11 +163,13 @@ export class Fore {
149
163
  /**
150
164
  * returns the next `fx-fore` element upwards in tree
151
165
  *
152
- * @param start
153
- * @returns {*}
166
+ * @param {HTMLElement|Text} start
167
+ * @returns {import('./fx-fore.js').FxFore}
154
168
  */
155
169
  static getFore(start) {
156
- return start.nodeType === Node.TEXT_NODE ? start.parentNode.closest('fx-fore'):start.closest('fx-fore');
170
+ return start.nodeType === Node.TEXT_NODE
171
+ ? start.parentNode.closest('fx-fore')
172
+ : start.closest('fx-fore');
157
173
  }
158
174
 
159
175
  static get ACTION_ELEMENTS() {
@@ -205,6 +221,10 @@ export class Fore {
205
221
  return 'http://www.w3.org/2002/xforms';
206
222
  }
207
223
 
224
+ /**
225
+ * @param {string} elementName
226
+ * @returns {boolean}
227
+ */
208
228
  static isActionElement(elementName) {
209
229
  return Fore.ACTION_ELEMENTS.includes(elementName);
210
230
  }
@@ -235,16 +255,14 @@ export class Fore {
235
255
  ];
236
256
  }
237
257
 
238
- static get MODEL_ELEMENTS(){
239
- return [
240
- 'FX-BIND',
241
- 'FX-FUNCTION',
242
- 'FX-MODEL',
243
- 'FX-INSTANCE',
244
- 'FX-SUBMISSION',
245
- ];
258
+ static get MODEL_ELEMENTS() {
259
+ return ['FX-BIND', 'FX-FUNCTION', 'FX-MODEL', 'FX-INSTANCE', 'FX-SUBMISSION'];
246
260
  }
247
261
 
262
+ /**
263
+ * @param {string} elementName
264
+ * @returns {boolean}
265
+ */
248
266
  static isUiElement(elementName) {
249
267
  const found = Fore.UI_ELEMENTS.includes(elementName);
250
268
  if (found) {
@@ -256,9 +274,9 @@ export class Fore {
256
274
  /**
257
275
  * recursively refreshes all UI Elements.
258
276
  *
259
- * @param startElement
260
- * @param force
261
- * @returns {Promise<unknown>}
277
+ * @param {HTMLElement} startElement
278
+ * @param {boolean} force
279
+ * @returns {Promise<void>}
262
280
  */
263
281
  static async refreshChildren(startElement, force) {
264
282
  const refreshed = new Promise(resolve => {
@@ -300,45 +318,46 @@ export class Fore {
300
318
  return refreshed;
301
319
  }
302
320
 
303
- static copyDom(inputElement){
321
+ static copyDom(inputElement) {
304
322
  console.time('convert');
305
323
  const target = new DOMParser().parseFromString('<fx-fore></fx-fore>', 'text/html');
306
- console.log('copyDom new doc',target);
307
- console.log('copyDom new body',target.body);
308
- console.log('copyDom new body',target.querySelector('fx-fore'));
324
+ console.log('copyDom new doc', target);
325
+ console.log('copyDom new body', target.body);
326
+ console.log('copyDom new body', target.querySelector('fx-fore'));
309
327
  const newFore = target.querySelector('fx-fore');
310
- this.convertFromSimple(inputElement,newFore);
328
+ this.convertFromSimple(inputElement, newFore);
311
329
  newFore.removeAttribute('convert');
312
330
  console.log('converted', newFore);
313
331
  console.timeEnd('convert');
314
332
  return newFore;
315
333
  }
316
- static convertFromSimple(startElement,targetElement){
334
+
335
+ static convertFromSimple(startElement, targetElement) {
317
336
  const children = startElement.childNodes;
318
337
  if (children) {
319
338
  Array.from(children).forEach(node => {
320
339
  const lookFor = `FX-${node.nodeName.toUpperCase()}`;
321
- if (Fore.MODEL_ELEMENTS.includes(lookFor)
322
- || Fore.UI_ELEMENTS.includes(lookFor)
323
- || Fore.ACTION_ELEMENTS.includes(lookFor)
340
+ if (
341
+ Fore.MODEL_ELEMENTS.includes(lookFor) ||
342
+ Fore.UI_ELEMENTS.includes(lookFor) ||
343
+ Fore.ACTION_ELEMENTS.includes(lookFor)
324
344
  ) {
325
345
  const conv = targetElement.ownerDocument.createElement(lookFor);
326
346
  console.log('conv', node, conv);
327
347
  targetElement.appendChild(conv);
328
- Fore.copyAttributes(node,conv);
329
- Fore.convertFromSimple(node,conv);
330
- } else{
331
-
332
- if(node.nodeType === Node.TEXT_NODE){
348
+ Fore.copyAttributes(node, conv);
349
+ Fore.convertFromSimple(node, conv);
350
+ } else {
351
+ if (node.nodeType === Node.TEXT_NODE) {
333
352
  const copied = targetElement.ownerDocument.createTextNode(node.textContent);
334
353
  targetElement.appendChild(copied);
335
354
  }
336
355
 
337
- if(node.nodeType === Node.ELEMENT_NODE){
356
+ if (node.nodeType === Node.ELEMENT_NODE) {
338
357
  const copied = targetElement.ownerDocument.createElement(node.nodeName);
339
358
  targetElement.appendChild(copied);
340
- Fore.copyAttributes(node,targetElement);
341
- Fore.convertFromSimple(node,copied);
359
+ Fore.copyAttributes(node, targetElement);
360
+ Fore.convertFromSimple(node, copied);
342
361
  }
343
362
  }
344
363
  });
@@ -347,10 +366,7 @@ export class Fore {
347
366
 
348
367
  static copyAttributes(source, target) {
349
368
  return Array.from(source.attributes).forEach(attribute => {
350
- target.setAttribute(
351
- attribute.nodeName,
352
- attribute.nodeValue,
353
- );
369
+ target.setAttribute(attribute.nodeName, attribute.nodeValue);
354
370
  });
355
371
  }
356
372
 
@@ -389,13 +405,13 @@ export class Fore {
389
405
  // return new DOMParser().parseFromString(htmlResponse, 'text/html');
390
406
  // return response.text();
391
407
  return response.text().then(result =>
392
- // console.log('xml ********', result);
393
- new DOMParser().parseFromString(result, 'text/html'),
408
+ // console.log('xml ********', result);
409
+ new DOMParser().parseFromString(result, 'text/html'),
394
410
  );
395
411
  }
396
412
  if (
397
- responseContentType.startsWith('text/plain') ||
398
- responseContentType.startsWith('text/markdown')
413
+ responseContentType.startsWith('text/plain') ||
414
+ responseContentType.startsWith('text/markdown')
399
415
  ) {
400
416
  // console.log("********** inside res plain *********");
401
417
  return response.text();
@@ -412,7 +428,7 @@ export class Fore {
412
428
  return 'done';
413
429
  }
414
430
 
415
- /*
431
+ /*
416
432
  static evaluateAttributeTemplateExpression(expr, node) {
417
433
  const matches = expr.match(/{[^}]*}/g);
418
434
  if (matches) {
@@ -471,7 +487,7 @@ export class Fore {
471
487
  }
472
488
 
473
489
  static async dispatch(target, eventName, detail) {
474
- if (!XPathUtil.contains(target?.ownerDocument, target)) {
490
+ if (!XPathUtil.contains(target?.ownerDocument, target)) {
475
491
  // The target is gone from the document. This happens when we are done with a refresh that removed the component
476
492
  return;
477
493
  }
@@ -491,17 +507,17 @@ export class Fore {
491
507
  // console.log('!!! DISPATCH_DONE', eventName);
492
508
  }
493
509
 
494
- static formatXml (xml) {
495
- var reg = /(>)(<)(\/*)/g;
496
- var wsexp = / *(.*) +\n/g;
497
- var contexp = /(<.+>)(.+\n)/g;
510
+ static formatXml(xml) {
511
+ const reg = /(>)(<)(\/*)/g;
512
+ const wsexp = / *(.*) +\n/g;
513
+ const contexp = /(<.+>)(.+\n)/g;
498
514
  xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
499
- var formatted = '';
500
- var lines = xml.split('\n');
501
- var indent = 0;
502
- var lastType = 'other';
515
+ let formatted = '';
516
+ const lines = xml.split('\n');
517
+ let indent = 0;
518
+ let lastType = 'other';
503
519
  // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions
504
- var transitions = {
520
+ const transitions = {
505
521
  'single->single': 0,
506
522
  'single->closing': -1,
507
523
  'single->opening': 0,
@@ -517,32 +533,34 @@ export class Fore {
517
533
  'other->single': 0,
518
534
  'other->closing': -1,
519
535
  'other->opening': 0,
520
- 'other->other': 0
536
+ 'other->other': 0,
521
537
  };
522
538
 
523
- for (var i = 0; i < lines.length; i++) {
524
- var ln = lines[i];
525
- var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex. <br />
526
- var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. </a>
527
- var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not <!something>)
528
- var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
529
- var fromTo = lastType + '->' + type;
539
+ for (let i = 0; i < lines.length; i++) {
540
+ const ln = lines[i];
541
+ const single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex. <br />
542
+ const closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. </a>
543
+ const opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not <!something>)
544
+ const type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
545
+ const fromTo = `${lastType}->${type}`;
530
546
  lastType = type;
531
- var padding = '';
547
+ let padding = '';
532
548
 
533
549
  indent += transitions[fromTo];
534
- for (var j = 0; j < indent; j++) {
550
+ for (let j = 0; j < indent; j++) {
535
551
  padding += ' ';
536
552
  }
537
553
 
538
- formatted += padding + ln + '\n';
554
+ formatted += `${padding + ln}\n`;
539
555
  }
540
556
  }
541
557
 
542
- static stringifiedComponent(element){
543
- return `<${element.localName} ${Array.from(element.attributes).map(attr=>`${attr.name}="${attr.value}"`).join(' ')}>…</${element.localName}>`;
558
+ static stringifiedComponent(element) {
559
+ return `<${element.localName} ${Array.from(element.attributes)
560
+ .map(attr => `${attr.name}="${attr.value}"`)
561
+ .join(' ')}>…</${element.localName}>`;
544
562
  }
545
- /*
563
+ /*
546
564
  static async loadForeFromUrl(hostElement, url) {
547
565
  // console.log('########## loading Fore from ', this.src, '##########');
548
566
  await fetch(url, {
@@ -1,28 +1,27 @@
1
1
  export function prettifyXml(source) {
2
- const xmlDoc = new DOMParser().parseFromString(source, 'application/xml');
3
- const xsltDoc = new DOMParser().parseFromString(
4
- [
5
- // describes how we want to modify the XML - indent everything
6
- '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
7
- ' <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>',
8
- ' <xsl:strip-space elements="*"/>',
9
- ' <xsl:template match="text()">', // change to just text() to strip space in text nodes
10
- ' <xsl:value-of select="normalize-space(.)"/>',
11
- ' </xsl:template>',
12
- ' <xsl:template match="node()|@*">',
13
- ' <xsl:copy>',
14
- ' <xsl:apply-templates select="node()|@*"/>',
15
- ' </xsl:copy>',
16
- ' </xsl:template>',
17
- '</xsl:stylesheet>',
18
- ].join('\n'),
19
- 'application/xml',
20
- );
2
+ const xmlDoc = new DOMParser().parseFromString(source, 'application/xml');
3
+ const xsltDoc = new DOMParser().parseFromString(
4
+ [
5
+ // describes how we want to modify the XML - indent everything
6
+ '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
7
+ ' <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>',
8
+ ' <xsl:strip-space elements="*"/>',
9
+ ' <xsl:template match="text()">', // change to just text() to strip space in text nodes
10
+ ' <xsl:value-of select="normalize-space(.)"/>',
11
+ ' </xsl:template>',
12
+ ' <xsl:template match="node()|@*">',
13
+ ' <xsl:copy>',
14
+ ' <xsl:apply-templates select="node()|@*"/>',
15
+ ' </xsl:copy>',
16
+ ' </xsl:template>',
17
+ '</xsl:stylesheet>',
18
+ ].join('\n'),
19
+ 'application/xml',
20
+ );
21
21
 
22
-
23
- const xsltProcessor = new XSLTProcessor();
24
- xsltProcessor.importStylesheet(xsltDoc);
25
- const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
26
- const resultXml = new XMLSerializer().serializeToString(resultDoc);
27
- return resultXml;
22
+ const xsltProcessor = new XSLTProcessor();
23
+ xsltProcessor.importStylesheet(xsltDoc);
24
+ const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
25
+ const resultXml = new XMLSerializer().serializeToString(resultDoc);
26
+ return resultXml;
28
27
  }
@@ -1,12 +1,10 @@
1
- import { registerCustomXPathFunction, createTypedValueFactory } from 'fontoxpath';
2
- import { foreElementMixin } from '../ForeElementMixin.js';
3
- import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation.js';
1
+ import ForeElementMixin from '../ForeElementMixin.js';
2
+ import registerFunction from './registerFunction.js';
4
3
 
5
4
  /**
6
5
  * Allows to extend a form with local custom functions.
7
- *
8
- * ` */
9
- export class FxFunction extends foreElementMixin(HTMLElement) {
6
+ */
7
+ export class FxFunction extends ForeElementMixin {
10
8
  constructor() {
11
9
  super();
12
10
  this.attachShadow({ mode: 'open' });
@@ -16,104 +14,13 @@ export class FxFunction extends foreElementMixin(HTMLElement) {
16
14
  this.style.display = 'none';
17
15
 
18
16
  this.signature = this.hasAttribute('signature') ? this.getAttribute('signature') : null;
19
- if (this.signature === null) {
20
- console.error('signature is a required attribute');
21
- }
22
17
  this.type = this.hasAttribute('type') ? this.getAttribute('type') : null;
23
- this.shadowRoot.innerHTML = `<slot></slot>`;
18
+ this.shadowRoot.innerHTML = '<slot></slot>';
24
19
 
25
20
  this.override = this.hasAttribute('override') ? this.getAttribute('override') : 'true';
26
21
  this.functionBody = this.innerText;
27
22
 
28
- const type = this.getAttribute('type') || 'text/xpath';
29
-
30
- // Parse the signature to something useful
31
- // Signature is of the form `my:sumproduct($p as xs:decimal*, $q as xs:decimal*) as xs:decimal` or local:something($a as item()*) as item()*
32
- const signatureParseResult = this.signature.match(
33
- /(?:(?<prefix>[^:]*):)?(?<localName>[^(]+)\((?<params>(?:\(\)|[^)])*)\)(?: as (?<returnType>.*))?/,
34
- );
35
-
36
- if (!signatureParseResult) {
37
- throw new Error(`Function signature ${this.signature} could not be parsed`);
38
- }
39
-
40
- const { prefix, localName, params, returnType } = signatureParseResult.groups;
41
-
42
- // TODO: lookup prefix
43
- const functionIdentifier =
44
- prefix === 'local' || !prefix
45
- ? { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName }
46
- : `${prefix}:${localName}`;
47
-
48
- // Make the function available globally w/o a prefix. See the functionNameResolver for for how
49
- // this is picked up
50
- if (!prefix) {
51
- globallyDeclaredFunctionLocalNames.push(localName);
52
- }
53
-
54
- const paramParts = params
55
- ? params.split(',').map(param => {
56
- const match = param.match(/(?<variableName>\$[^\s]+)(?:\sas\s(?<varType>.+))/);
57
- if (!match) {
58
- throw new Error(`Param ${param} could not be parsed`);
59
- }
60
- const { variableName, varType } = match.groups;
61
- return {
62
- variableName,
63
- variableType: varType || 'item()*',
64
- };
65
- })
66
- : [];
67
-
68
- switch (type) {
69
- case 'text/javascript': {
70
- // eslint-disable-next-line no-new-func
71
- const fun = new Function(
72
- '_domFacade',
73
- ...paramParts.map(paramPart => paramPart.variableName),
74
- 'form',
75
- this.functionBody,
76
- );
77
- registerCustomXPathFunction(
78
- functionIdentifier,
79
- paramParts.map(paramPart => paramPart.variableType),
80
- returnType || 'item()*',
81
- (...args) => fun.apply(this.getInScopeContext(), [...args, this.getOwnerForm()]),
82
- );
83
- break;
84
- }
85
-
86
- case 'text/xquf':
87
- case 'text/xquery':
88
- case 'text/xpath': {
89
- const typedValueFactories = paramParts.map(param => createTypedValueFactory(param.variableType));
90
- const language = type === 'text/xpath' ?
91
- 'XPath3.1' : type === 'text/xquery' ?
92
- 'XQuery3.1' : 'XQueryUpdate3.1';
93
- const fun = (domFacade, ...args) =>
94
- evaluateXPath(
95
- this.functionBody,
96
- this.getInScopeContext(),
97
- this.getOwnerForm(),
98
- paramParts.reduce((variablesByName, paramPart, i) => {
99
- // Because we know the XPath type here (from the function declaration) we do not have to depend on the implicit typings
100
- variablesByName[paramPart.variableName.replace('$', '')] = typedValueFactories[i](args[i]);
101
- return variablesByName;
102
- }, {}),
103
- {language}
104
- );
105
- registerCustomXPathFunction(
106
- functionIdentifier,
107
- paramParts.map(paramPart => paramPart.variableType),
108
- returnType || 'item()*',
109
- fun,
110
- );
111
- break;
112
- }
113
-
114
- default:
115
- throw new Error(`Unexpected mimetype ${type} for function`);
116
- }
23
+ registerFunction(this, this);
117
24
  }
118
25
  }
119
26
  if (!customElements.get('fx-function')) {