@formique/semantq 1.0.7 → 1.0.9
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.
- package/LowCodeParser.js +1576 -0
- package/astToFormique.js +1054 -0
- package/formique-semantq.js +461 -229
- package/package.json +1 -1
package/astToFormique.js
ADDED
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FormiqueParser - A comprehensive form definition parser that converts an Abstract Syntax Tree (AST)
|
|
5
|
+
* representation into a structured form schema with validation rules, input types, and conditional logic.
|
|
6
|
+
*
|
|
7
|
+
* Handles complex form structures including:
|
|
8
|
+
* - Dynamic single-select fields with scenario-based options
|
|
9
|
+
* - Input type inference from field names and attributes
|
|
10
|
+
* - Validation rules extraction and categorization
|
|
11
|
+
* - Conditional field dependencies (dependsOn/dependents)
|
|
12
|
+
* - Multiple input types (text, email, select, radio, checkbox, file, etc.)
|
|
13
|
+
* - Form-level settings and parameters
|
|
14
|
+
* - Option lists for selectable inputs
|
|
15
|
+
* - Required field detection via asterisk notation
|
|
16
|
+
*
|
|
17
|
+
* The parser processes AST nodes to build a complete form configuration including:
|
|
18
|
+
* - Form schema with field definitions, validations, and attributes
|
|
19
|
+
* - Form settings for behavioral configuration
|
|
20
|
+
* - Form parameters for HTML form attributes
|
|
21
|
+
* - Support for complex field relationships and dynamic content
|
|
22
|
+
*
|
|
23
|
+
* @class
|
|
24
|
+
* @param {Object} ast - The Abstract Syntax Tree representing the form structure
|
|
25
|
+
* @returns {Object} Configuration object containing formSchema, formSettings, and formParams
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export default class astToFormique{
|
|
29
|
+
constructor (ast) {
|
|
30
|
+
this.ast = ast;
|
|
31
|
+
this.formSchema=[];
|
|
32
|
+
this.formSettings={};
|
|
33
|
+
this.formParams={};
|
|
34
|
+
|
|
35
|
+
this.formAttributes = [
|
|
36
|
+
"action",
|
|
37
|
+
"method",
|
|
38
|
+
"enctype",
|
|
39
|
+
"name",
|
|
40
|
+
"target",
|
|
41
|
+
"autocomplete",
|
|
42
|
+
"novalidate",
|
|
43
|
+
"rel",
|
|
44
|
+
"accept-charset",
|
|
45
|
+
"id",
|
|
46
|
+
"class",
|
|
47
|
+
"style",
|
|
48
|
+
"title",
|
|
49
|
+
"lang",
|
|
50
|
+
"dir",
|
|
51
|
+
"hidden",
|
|
52
|
+
"tabindex",
|
|
53
|
+
"accesskey",
|
|
54
|
+
"draggable",
|
|
55
|
+
"contenteditable",
|
|
56
|
+
"spellcheck",
|
|
57
|
+
"onsubmit",
|
|
58
|
+
"onreset",
|
|
59
|
+
"onchange",
|
|
60
|
+
"oninput",
|
|
61
|
+
"onfocus",
|
|
62
|
+
"onblur",
|
|
63
|
+
"onkeydown",
|
|
64
|
+
"onkeyup",
|
|
65
|
+
"onclick",
|
|
66
|
+
"ondblclick",
|
|
67
|
+
"onmouseover",
|
|
68
|
+
"onmouseout",
|
|
69
|
+
"aria-label",
|
|
70
|
+
"aria-labelledby",
|
|
71
|
+
"aria-describedby",
|
|
72
|
+
"role"
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
this.inputAttributes = [
|
|
76
|
+
"id",
|
|
77
|
+
"class",
|
|
78
|
+
"type",
|
|
79
|
+
"value",
|
|
80
|
+
"name",
|
|
81
|
+
"placeholder",
|
|
82
|
+
"autofocus",
|
|
83
|
+
"size",
|
|
84
|
+
"accept",
|
|
85
|
+
"form",
|
|
86
|
+
"list"
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
this.validationAttributes = [
|
|
91
|
+
// Basic validations
|
|
92
|
+
'required',
|
|
93
|
+
'disabled',
|
|
94
|
+
'readonly',
|
|
95
|
+
|
|
96
|
+
// Text/pattern validations
|
|
97
|
+
'minlength',
|
|
98
|
+
'maxlength',
|
|
99
|
+
'pattern',
|
|
100
|
+
|
|
101
|
+
// Numeric validations
|
|
102
|
+
'min',
|
|
103
|
+
'max',
|
|
104
|
+
'step',
|
|
105
|
+
|
|
106
|
+
// Date/time validations
|
|
107
|
+
'min',
|
|
108
|
+
'max',
|
|
109
|
+
|
|
110
|
+
// File validations
|
|
111
|
+
'accept',
|
|
112
|
+
'multiple',
|
|
113
|
+
'filesize', // Note: Typically implemented via JavaScript
|
|
114
|
+
|
|
115
|
+
// Special input types
|
|
116
|
+
'checked', // For checkboxes/radios
|
|
117
|
+
'selected', // For options
|
|
118
|
+
'placeholder', // Not validation but affects input
|
|
119
|
+
|
|
120
|
+
// Custom data attributes (commonly used for validation)
|
|
121
|
+
'data-validate',
|
|
122
|
+
'data-required',
|
|
123
|
+
'data-min',
|
|
124
|
+
'data-max',
|
|
125
|
+
'data-minlength',
|
|
126
|
+
'data-maxlength',
|
|
127
|
+
'data-pattern',
|
|
128
|
+
'data-error',
|
|
129
|
+
'data-validate-on',
|
|
130
|
+
'data-equal-to',
|
|
131
|
+
|
|
132
|
+
// ARIA validation attributes
|
|
133
|
+
'aria-required',
|
|
134
|
+
'aria-invalid',
|
|
135
|
+
|
|
136
|
+
// Form-level validation
|
|
137
|
+
'novalidate',
|
|
138
|
+
'formnovalidate'
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
this.ignoreAttributes =[
|
|
143
|
+
|
|
144
|
+
'oneof',
|
|
145
|
+
'one',
|
|
146
|
+
'manyof',
|
|
147
|
+
'many',
|
|
148
|
+
'radio',
|
|
149
|
+
'select',
|
|
150
|
+
'mutli-select',
|
|
151
|
+
'multiselect',
|
|
152
|
+
'multipleselect',
|
|
153
|
+
'multiple-select',
|
|
154
|
+
'multiple',
|
|
155
|
+
'checkbox',
|
|
156
|
+
'selected',
|
|
157
|
+
'default'
|
|
158
|
+
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
this.inputTypeMaps = {
|
|
163
|
+
oneof: 'radio',
|
|
164
|
+
one: 'radio',
|
|
165
|
+
radio: 'radio',
|
|
166
|
+
select: 'singleSelect',
|
|
167
|
+
singleSelect: 'singleSelect', // optional addition
|
|
168
|
+
manyof: 'checkbox',
|
|
169
|
+
many: 'checkbox',
|
|
170
|
+
checkbox: 'checkbox',
|
|
171
|
+
'multi-select': 'multipleSelect',
|
|
172
|
+
multiselect: 'multipleSelect',
|
|
173
|
+
multipleselect: 'multipleSelect',
|
|
174
|
+
'multiple-select': 'multipleSelect',
|
|
175
|
+
multiple: 'multipleSelect',
|
|
176
|
+
selectMany: 'multipleSelect',
|
|
177
|
+
selectOne:'singleSelect',
|
|
178
|
+
manyselect: 'multipleSelect',
|
|
179
|
+
oneselect:'singleSelect',
|
|
180
|
+
selectmany: 'multipleSelect',
|
|
181
|
+
selectone:'singleSelect',
|
|
182
|
+
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.regularInputTypes = [
|
|
186
|
+
'text',
|
|
187
|
+
'password',
|
|
188
|
+
'email',
|
|
189
|
+
'number',
|
|
190
|
+
'range',
|
|
191
|
+
'date',
|
|
192
|
+
'datetime-local',
|
|
193
|
+
'time',
|
|
194
|
+
'month',
|
|
195
|
+
'week',
|
|
196
|
+
'search',
|
|
197
|
+
'tel',
|
|
198
|
+
'url',
|
|
199
|
+
'color',
|
|
200
|
+
'checkbox',
|
|
201
|
+
'radio',
|
|
202
|
+
'file',
|
|
203
|
+
'hidden',
|
|
204
|
+
'submit',
|
|
205
|
+
'reset',
|
|
206
|
+
'button',
|
|
207
|
+
'image'
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
// Exhaustive type inference map
|
|
211
|
+
this.typeInferenceRules = {
|
|
212
|
+
// Email fields
|
|
213
|
+
email: { type: 'email', priority: 10 },
|
|
214
|
+
'e-mail': { type: 'email', priority: 9 },
|
|
215
|
+
mail: { type: 'email', priority: 8 },
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
// Name fields (all text type)
|
|
219
|
+
name: { type: 'text', subtype: 'name', priority: 10 },
|
|
220
|
+
'first-name': { type: 'text', subtype: 'given-name', priority: 10 },
|
|
221
|
+
'firstname': { type: 'text', subtype: 'given-name', priority: 9 },
|
|
222
|
+
'given-name': { type: 'text', subtype: 'given-name', priority: 10 },
|
|
223
|
+
'last-name': { type: 'text', subtype: 'family-name', priority: 10 },
|
|
224
|
+
'lastname': { type: 'text', subtype: 'family-name', priority: 9 },
|
|
225
|
+
surname: { type: 'text', subtype: 'family-name', priority: 10 },
|
|
226
|
+
'family-name': { type: 'text', subtype: 'family-name', priority: 10 },
|
|
227
|
+
'full-name': { type: 'text', subtype: 'full-name', priority: 10 },
|
|
228
|
+
'middle-name': { type: 'text', subtype: 'additional-name', priority: 9 },
|
|
229
|
+
'middle-initial': { type: 'text', subtype: 'additional-name', priority: 8 },
|
|
230
|
+
nickname: { type: 'text', subtype: 'nickname', priority: 9 },
|
|
231
|
+
username: { type: 'text', subtype: 'username', priority: 9 },
|
|
232
|
+
displayname: { type: 'text', subtype: 'display-name', priority: 8 },
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
// General text fields
|
|
236
|
+
title: { type: 'text', subtype: 'title', priority: 9 },
|
|
237
|
+
subject: { type: 'text', subtype: 'subject', priority: 8 },
|
|
238
|
+
description: { type: 'text', subtype: 'description', priority: 9 },
|
|
239
|
+
note: { type: 'text', subtype: 'note', priority: 8 },
|
|
240
|
+
bio: { type: 'text', subtype: 'bio', priority: 8 },
|
|
241
|
+
address: { type: 'text', subtype: 'address', priority: 9 },
|
|
242
|
+
city: { type: 'text', subtype: 'city', priority: 9 },
|
|
243
|
+
state: { type: 'text', subtype: 'state', priority: 9 },
|
|
244
|
+
province: { type: 'text', subtype: 'province', priority: 9 },
|
|
245
|
+
country: { type: 'text', subtype: 'country', priority: 9 },
|
|
246
|
+
zipcode: { type: 'text', subtype: 'postal-code', priority: 9 },
|
|
247
|
+
'postal-code': { type: 'text', subtype: 'postal-code', priority: 9 },
|
|
248
|
+
company: { type: 'text', subtype: 'organization', priority: 9 },
|
|
249
|
+
organization: { type: 'text', subtype: 'organization', priority: 9 },
|
|
250
|
+
job: { type: 'text', subtype: 'job-title', priority: 8 },
|
|
251
|
+
'job-title': { type: 'text', subtype: 'job-title', priority: 9 },
|
|
252
|
+
occupation: { type: 'text', subtype: 'job-title', priority: 8 },
|
|
253
|
+
message: { type: 'textarea', subtype: 'message', priority: 9 },
|
|
254
|
+
comment: { type: 'textarea', subtype: 'comment', priority: 8 },
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
// Telephone fields
|
|
259
|
+
tel: { type: 'tel', priority: 10 },
|
|
260
|
+
phone: { type: 'tel', priority: 10 },
|
|
261
|
+
mobile: { type: 'tel', priority: 10 },
|
|
262
|
+
telephone: { type: 'tel', priority: 10 },
|
|
263
|
+
cell: { type: 'tel', priority: 9 },
|
|
264
|
+
'cell-phone': { type: 'tel', priority: 9 },
|
|
265
|
+
|
|
266
|
+
// Numeric fields
|
|
267
|
+
// Numeric fields - Add these to your existing object
|
|
268
|
+
count: { type: 'number', priority: 9 },
|
|
269
|
+
price: { type: 'number', priority: 9 },
|
|
270
|
+
total: { type: 'number', priority: 8 },
|
|
271
|
+
amount: { type: 'number', priority: 9 },
|
|
272
|
+
quantity: { type: 'number', priority: 9 },
|
|
273
|
+
qty: { type: 'number', priority: 8 },
|
|
274
|
+
age: { type: 'number', priority: 8 },
|
|
275
|
+
sum: { type: 'number', priority: 7 },
|
|
276
|
+
value: { type: 'number', priority: 7 },
|
|
277
|
+
percent: { type: 'number', priority: 8 },
|
|
278
|
+
percentage: { type: 'number', priority: 8 },
|
|
279
|
+
discount: { type: 'number', priority: 7 },
|
|
280
|
+
|
|
281
|
+
// Currency fields (also numeric but might need special handling)
|
|
282
|
+
cost: { type: 'number', priority: 9 },
|
|
283
|
+
payment: { type: 'number', priority: 8 },
|
|
284
|
+
salary: { type: 'number', priority: 8 },
|
|
285
|
+
fee: { type: 'number', priority: 8 },
|
|
286
|
+
|
|
287
|
+
// Date fields
|
|
288
|
+
date: { type: 'date', priority: 10 },
|
|
289
|
+
dob: { type: 'date', priority: 9 },
|
|
290
|
+
'birth-date': { type: 'date', priority: 8 },
|
|
291
|
+
'start-date': { type: 'date', priority: 7 },
|
|
292
|
+
'end-date': { type: 'date', priority: 7 },
|
|
293
|
+
|
|
294
|
+
// Password fields
|
|
295
|
+
password: { type: 'password', priority: 10 },
|
|
296
|
+
pwd: { type: 'password', priority: 8 },
|
|
297
|
+
secret: { type: 'password', priority: 6 },
|
|
298
|
+
|
|
299
|
+
// URL fields
|
|
300
|
+
url: { type: 'url', priority: 10 },
|
|
301
|
+
website: { type: 'url', priority: 8 },
|
|
302
|
+
link: { type: 'url', priority: 7 },
|
|
303
|
+
|
|
304
|
+
// Special cases
|
|
305
|
+
color: { type: 'color', priority: 10 },
|
|
306
|
+
search: { type: 'search', priority: 10 },
|
|
307
|
+
range: { type: 'range', priority: 10 },
|
|
308
|
+
|
|
309
|
+
// File uploads
|
|
310
|
+
file: { type: 'file', priority: 10 },
|
|
311
|
+
upload: { type: 'file', priority: 9 },
|
|
312
|
+
document: { type: 'file', priority: 8 },
|
|
313
|
+
attachment: { type: 'file', priority: 8 },
|
|
314
|
+
resume: { type: 'file', priority: 7 },
|
|
315
|
+
cv: { type: 'file', priority: 7 },
|
|
316
|
+
portfolio: { type: 'file', priority: 7 },
|
|
317
|
+
|
|
318
|
+
// Image-specific
|
|
319
|
+
image: { type: 'file', accept: 'image/*', priority: 10 },
|
|
320
|
+
photo: { type: 'file', accept: 'image/*', priority: 9 },
|
|
321
|
+
avatar: { type: 'file', accept: 'image/*', priority: 9 },
|
|
322
|
+
picture: { type: 'file', accept: 'image/*', priority: 8 },
|
|
323
|
+
logo: { type: 'file', accept: 'image/*', priority: 8 },
|
|
324
|
+
banner: { type: 'file', accept: 'image/*', priority: 7 },
|
|
325
|
+
thumbnail: { type: 'file', accept: 'image/*', priority: 7 },
|
|
326
|
+
|
|
327
|
+
// Media files
|
|
328
|
+
video: { type: 'file', accept: 'video/*', priority: 9 },
|
|
329
|
+
audio: { type: 'file', accept: 'audio/*', priority: 9 },
|
|
330
|
+
recording: { type: 'file', accept: 'audio/*', priority: 7 },
|
|
331
|
+
|
|
332
|
+
// Specific file types
|
|
333
|
+
pdf: { type: 'file', accept: '.pdf', priority: 8 },
|
|
334
|
+
spreadsheet: { type: 'file', accept: '.csv,.xls,.xlsx', priority: 7 },
|
|
335
|
+
excel: { type: 'file', accept: '.xls,.xlsx', priority: 8 },
|
|
336
|
+
word: { type: 'file', accept: '.doc,.docx', priority: 8 },
|
|
337
|
+
presentation: { type: 'file', accept: '.ppt,.pptx', priority: 7 },
|
|
338
|
+
|
|
339
|
+
// Multiple files
|
|
340
|
+
files: { type: 'file', multiple: true, priority: 8 },
|
|
341
|
+
images: { type: 'file', accept: 'image/*', multiple: true, priority: 8 },
|
|
342
|
+
gallery: { type: 'file', accept: 'image/*', multiple: true, priority: 7 },
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
gender: { type: 'radio', priority: 10 },
|
|
346
|
+
sex: { type: 'radio', priority: 9 },
|
|
347
|
+
title: { type: 'select', priority: 8 },
|
|
348
|
+
pronoun: { type: 'select', priority: 8 },
|
|
349
|
+
salutation: { type: 'select', priority: 7 },
|
|
350
|
+
|
|
351
|
+
// Boolean (Yes/No)
|
|
352
|
+
newsletter: { type: 'radio', priority: 7 },
|
|
353
|
+
agree: { type: 'radio', priority: 6 },
|
|
354
|
+
smoker: { type: 'radio', priority: 5 },
|
|
355
|
+
terms: { type: 'radio', priority: 6 },
|
|
356
|
+
|
|
357
|
+
// Categories
|
|
358
|
+
maritalstatus: { type: 'select', priority: 7 },
|
|
359
|
+
employment: { type: 'select', priority: 7 },
|
|
360
|
+
education: { type: 'select', priority: 6 },
|
|
361
|
+
status: { type: 'select', priority: 5 },
|
|
362
|
+
|
|
363
|
+
// Location
|
|
364
|
+
country: { type: 'select', priority: 9 },
|
|
365
|
+
language: { type: 'select', priority: 6 },
|
|
366
|
+
region: { type: 'select', priority: 5 },
|
|
367
|
+
state: { type: 'select', priority: 5 },
|
|
368
|
+
|
|
369
|
+
// Surveys/Ratings
|
|
370
|
+
rating: { type: 'radio', priority: 5 },
|
|
371
|
+
satisfaction: { type: 'radio', priority: 5 },
|
|
372
|
+
feedback: { type: 'radio', priority: 4 },
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
reset: { type: 'reset', priority: 10 },
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// Default fallback
|
|
381
|
+
this.defaultType = 'text';
|
|
382
|
+
|
|
383
|
+
this.traverse();
|
|
384
|
+
this.addSubmit();
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
formSchema: this.formSchema,
|
|
390
|
+
formSettings: this.formSettings,
|
|
391
|
+
formParams: this.formParams
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
// formDirective , formProperties, formProperty, formFields,
|
|
398
|
+
// optionsAttribute, fieldsAttribute
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
inferInputType(fieldName) {
|
|
402
|
+
if (!fieldName) return this.defaultType;
|
|
403
|
+
|
|
404
|
+
const lowerName = fieldName.toLowerCase().trim();
|
|
405
|
+
const matches = [];
|
|
406
|
+
|
|
407
|
+
// Check for exact matches first
|
|
408
|
+
if (this.typeInferenceRules[lowerName]) {
|
|
409
|
+
return this.typeInferenceRules[lowerName].type;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Check for partial matches (e.g., "userEmail" contains "email")
|
|
413
|
+
for (const [key, rule] of Object.entries(this.typeInferenceRules)) {
|
|
414
|
+
if (lowerName.includes(key)) {
|
|
415
|
+
matches.push({ ...rule, key });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// If multiple matches, use the one with highest priority
|
|
420
|
+
if (matches.length > 0) {
|
|
421
|
+
matches.sort((a, b) => b.priority - a.priority);
|
|
422
|
+
return matches[0].type;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Check for common patterns
|
|
426
|
+
if (/.*password.*/i.test(fieldName)) return 'password';
|
|
427
|
+
if (/.*(mail|email).*/i.test(fieldName)) return 'email';
|
|
428
|
+
if (/.*(tel|phone|mobile).*/i.test(fieldName)) return 'tel';
|
|
429
|
+
if (/.*(date|dob|birth).*/i.test(fieldName)) return 'date';
|
|
430
|
+
|
|
431
|
+
return this.defaultType;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
cleanFieldName(str) {
|
|
437
|
+
const [rawName = '', rawType = ''] = str.split(':');
|
|
438
|
+
|
|
439
|
+
const input_name = rawName
|
|
440
|
+
.trim()
|
|
441
|
+
.replace(/[^\w-]/g, ''); // keeps letters, digits, underscores, hyphens
|
|
442
|
+
|
|
443
|
+
const input_type = rawType.trim(); // untouched for now
|
|
444
|
+
|
|
445
|
+
return { input_name, input_type };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
cleanToInputType(str) {
|
|
450
|
+
const cleaned = str.trim().toLowerCase();
|
|
451
|
+
|
|
452
|
+
if (cleaned.includes('datetime-local')) {
|
|
453
|
+
// Keep only letters and hyphen
|
|
454
|
+
return cleaned.replace(/[^a-z-]/g, '');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Otherwise, keep only letters
|
|
458
|
+
return cleaned.replace(/[^a-z]/g, '');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
toTitleCase(str) {
|
|
464
|
+
return str
|
|
465
|
+
.toLowerCase()
|
|
466
|
+
.split(/[\s_-]+/) // split by space, dash, or underscore
|
|
467
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
468
|
+
.join(' ');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
isRequired(str) {
|
|
472
|
+
return str.replace(/\s+/g, '').includes('*');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
traverse() {
|
|
477
|
+
const nodeHandlers = {
|
|
478
|
+
FormDirective: this.buildDirective.bind(this),
|
|
479
|
+
FormProperties: this.buildProperties.bind(this),
|
|
480
|
+
FormFields: this.buildFields.bind(this),
|
|
481
|
+
FormField: this.buildField.bind(this),
|
|
482
|
+
OptionsAttribute: this.buildOptionsAttribute.bind(this)
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const traverseNode = (node) => {
|
|
486
|
+
if (!node || typeof node !== 'object') return;
|
|
487
|
+
|
|
488
|
+
const handler = nodeHandlers[node.type];
|
|
489
|
+
if (handler) {
|
|
490
|
+
handler(node);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Handle nested structures specific to this AST format
|
|
494
|
+
if (node.properties && node.properties.properties && Array.isArray(node.properties.properties)) {
|
|
495
|
+
node.properties.properties.forEach(traverseNode);
|
|
496
|
+
}
|
|
497
|
+
if (node.fields && Array.isArray(node.fields)) {
|
|
498
|
+
node.fields.forEach(traverseNode);
|
|
499
|
+
}
|
|
500
|
+
if (node.attributes && Array.isArray(node.attributes)) {
|
|
501
|
+
node.attributes.forEach(traverseNode);
|
|
502
|
+
}
|
|
503
|
+
if (node.values && Array.isArray(node.values)) {
|
|
504
|
+
node.values.forEach(traverseNode);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// Handle the array-based AST structure
|
|
509
|
+
if (Array.isArray(this.ast)) {
|
|
510
|
+
this.ast.forEach(traverseNode);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
extractAttributeKeys(nodes) {
|
|
516
|
+
|
|
517
|
+
if (!Array.isArray(nodes)) {
|
|
518
|
+
throw new Error('Input must be an array of nodes');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return nodes
|
|
522
|
+
// Step 1: Filter only FieldAttribute nodes
|
|
523
|
+
.filter(node => node?.type === 'FieldAttribute')
|
|
524
|
+
|
|
525
|
+
// Step 2: Extract keys safely
|
|
526
|
+
.map(node => node?.key)
|
|
527
|
+
|
|
528
|
+
// Step 3: Remove any undefined/null keys
|
|
529
|
+
.filter(Boolean)
|
|
530
|
+
|
|
531
|
+
// Step 4: Remove potential duplicates (optional)
|
|
532
|
+
.filter((key, index, self) => self.indexOf(key) === index);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
extractOptionValues(attributes) {
|
|
537
|
+
if (!Array.isArray(attributes)) {
|
|
538
|
+
throw new Error('Input must be an array of attributes');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Get all option values from all OptionsAttributes and remove duplicates
|
|
542
|
+
const allOptions = attributes
|
|
543
|
+
.filter(attr => attr?.type === 'OptionsAttribute')
|
|
544
|
+
.flatMap(attr => attr.values || [])
|
|
545
|
+
.map(option => option?.value)
|
|
546
|
+
.filter(Boolean);
|
|
547
|
+
|
|
548
|
+
// Remove duplicates by using a Set
|
|
549
|
+
return [...new Set(allOptions)];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
extractDependentValues(attributes) {
|
|
555
|
+
if (!Array.isArray(attributes)) {
|
|
556
|
+
throw new Error('Input must be an array of attributes');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return attributes
|
|
560
|
+
// Find the OptionsAttribute node
|
|
561
|
+
.filter(attr => attr?.type === 'OptionsAttribute' && attr?.key === 'dependents')
|
|
562
|
+
// Get the values array
|
|
563
|
+
.flatMap(attr => attr.values || [])
|
|
564
|
+
// Extract each option's value
|
|
565
|
+
.map(option => option?.value)
|
|
566
|
+
// Remove any undefined/null values
|
|
567
|
+
.filter(Boolean);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
inputTypeResolver(fieldName, attributeKeys) {
|
|
572
|
+
|
|
573
|
+
// first option - handle dynamicSingleSelect
|
|
574
|
+
//console.log("CHK",fieldName);
|
|
575
|
+
if (fieldName.includes('-')) {
|
|
576
|
+
return "dynamicSingleSelect"; //
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
// second option - explicit field name directive
|
|
582
|
+
if (fieldName.includes(':')) {
|
|
583
|
+
const chunks = fieldName.split(':');
|
|
584
|
+
//console.log("LAPHA",chunks[1]);
|
|
585
|
+
return chunks[1]; // e.g., 'date' from 'dob:date'
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Third option - low code type definition
|
|
589
|
+
const matchedKey = attributeKeys.find(key => key in this.inputTypeMaps);
|
|
590
|
+
if (matchedKey) {
|
|
591
|
+
//console.log("HERE",matchedKey);
|
|
592
|
+
// console.log("HERE 2",this.inputTypeMaps[matchedKey]);
|
|
593
|
+
|
|
594
|
+
return this.inputTypeMaps[matchedKey];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Fourth 3: inference with text fall back
|
|
598
|
+
return this.inferInputType(fieldName);
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
getOptionValuesByKey(attributes, targetKey) {
|
|
605
|
+
if (!Array.isArray(attributes)) throw new Error('Input must be an array of attributes');
|
|
606
|
+
if (!targetKey) throw new Error('Target key is required');
|
|
607
|
+
|
|
608
|
+
const optionsAttribute = attributes.find(
|
|
609
|
+
attr => attr?.type === 'OptionsAttribute' && attr?.key === targetKey
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
return optionsAttribute?.values
|
|
613
|
+
? optionsAttribute.values
|
|
614
|
+
.map(option => option?.value)
|
|
615
|
+
.filter(Boolean)
|
|
616
|
+
.map(value => value.toLowerCase()) // Normalize to lowercase
|
|
617
|
+
: [];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
buildDynamicSingleSelect(node, rawFieldName) {
|
|
623
|
+
|
|
624
|
+
const cleanString = this.cleanFieldName(rawFieldName);
|
|
625
|
+
const fieldName = cleanString.input_name;
|
|
626
|
+
|
|
627
|
+
let fieldSchema = [];
|
|
628
|
+
fieldSchema.push('dynamicSingleSelect',fieldName, this.toTitleCase(fieldName));
|
|
629
|
+
|
|
630
|
+
let validations;
|
|
631
|
+
let attributes;
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
//console.log("HERE",validations);
|
|
635
|
+
let inputParams;
|
|
636
|
+
|
|
637
|
+
if (node.attributes.length > 0 ) {
|
|
638
|
+
inputParams = this.handleAttributes(node.attributes);
|
|
639
|
+
//console.log("InputParams",inputParams);
|
|
640
|
+
} else {
|
|
641
|
+
inputParams = { validations: {}, attributes: {} }
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
//console.log("InputParams",inputParams);
|
|
646
|
+
|
|
647
|
+
validations = inputParams.validations;
|
|
648
|
+
attributes = inputParams.attributes;
|
|
649
|
+
|
|
650
|
+
if (this.isRequired(rawFieldName)) {
|
|
651
|
+
validations['required'] = true;
|
|
652
|
+
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
//console.log("VALS",validations);
|
|
657
|
+
//console.log("ATTRs",attributes);
|
|
658
|
+
|
|
659
|
+
fieldSchema.push(validations)
|
|
660
|
+
fieldSchema.push(attributes)
|
|
661
|
+
|
|
662
|
+
/// NOW BUILD SCENARIO (SELECT STATE) BLOCKS
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
/// E.G. Countries for which we want display states reactively
|
|
666
|
+
const optionValues = this.extractOptionValues(node.attributes);
|
|
667
|
+
//console.log("optionValues",optionValues);
|
|
668
|
+
|
|
669
|
+
let scenarioBlocks =[];
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
if (optionValues.length > 0 ) {
|
|
673
|
+
|
|
674
|
+
optionValues.forEach(option => {
|
|
675
|
+
|
|
676
|
+
let schema = {};
|
|
677
|
+
|
|
678
|
+
schema['id']= option.toLowerCase();
|
|
679
|
+
schema['label']= option;
|
|
680
|
+
|
|
681
|
+
/// add options now
|
|
682
|
+
const keyOptions = this.getOptionValuesByKey(node.attributes, option);
|
|
683
|
+
//console.log(keyOptions);
|
|
684
|
+
let options = [];
|
|
685
|
+
|
|
686
|
+
if (keyOptions.length > 0) {
|
|
687
|
+
keyOptions.forEach(subOption => {
|
|
688
|
+
options.push({value: subOption.toLowerCase(), label: this.toTitleCase(subOption)})
|
|
689
|
+
})
|
|
690
|
+
schema['options']= options;
|
|
691
|
+
scenarioBlocks.push(schema);
|
|
692
|
+
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
// now get option options
|
|
698
|
+
//scenarioBlock.push(schema);
|
|
699
|
+
fieldSchema.push(scenarioBlocks);
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
// options.push({value: option, label: this.toTitleCase(option)})
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
this.formSchema.push(fieldSchema);
|
|
706
|
+
|
|
707
|
+
//console.log("THERE",JSON.stringify(fieldSchema,null,2));
|
|
708
|
+
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
// Builder methods - implement these according to your needs
|
|
720
|
+
buildDirective(node) {
|
|
721
|
+
//console.log(`Processing FormDirective: ${node.name.value}`);
|
|
722
|
+
|
|
723
|
+
this.formParams['id'] = node.name.value;
|
|
724
|
+
// Handle directive specific logic
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
buildProperties(node) {
|
|
728
|
+
// console.log(`Processing FormProperties with ${node.properties.length} properties`);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
buildProperty(node) {
|
|
732
|
+
//console.log(`Processing FormProperty: ${node.key.value} = ${node.value.value}`);
|
|
733
|
+
const key = node.key.value;
|
|
734
|
+
const val = node.value.values[0].value;
|
|
735
|
+
|
|
736
|
+
//console.log(node);
|
|
737
|
+
|
|
738
|
+
// if this is regular form attribute then it goes to formParams
|
|
739
|
+
if (this.formAttributes.includes(key)) {
|
|
740
|
+
this.formParams[key] = val
|
|
741
|
+
} else {
|
|
742
|
+
|
|
743
|
+
if (key === 'sendTo') {
|
|
744
|
+
const sendToEmails = node.value.values.map(option => option.value);
|
|
745
|
+
this.formSettings[key] = sendToEmails
|
|
746
|
+
} else
|
|
747
|
+
{
|
|
748
|
+
this.formSettings[key] = val
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
//console.log(this.formSettings);
|
|
755
|
+
//console.log(this.formParams);
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
buildFields(node) {
|
|
761
|
+
//console.log(`Processing FormFields with ${node.fields} fields`);
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
/* eslint-disable no-unused-vars */
|
|
771
|
+
/* eslint-disable no-useless-escape */
|
|
772
|
+
// The rest of the class definition from Part 1 goes here...
|
|
773
|
+
// ...
|
|
774
|
+
// ...
|
|
775
|
+
|
|
776
|
+
buildField(node) {
|
|
777
|
+
const rawFieldName = node.name;
|
|
778
|
+
const cleanString = this.cleanFieldName(rawFieldName);
|
|
779
|
+
const cleanFieldName = cleanString.input_name;
|
|
780
|
+
|
|
781
|
+
let fieldType;
|
|
782
|
+
|
|
783
|
+
if (cleanString.input_type) {
|
|
784
|
+
fieldType = this.cleanToInputType(cleanString.input_type);
|
|
785
|
+
} else {
|
|
786
|
+
let attributeKeys;
|
|
787
|
+
if (node.attributes.length > 0) {
|
|
788
|
+
attributeKeys = this.extractAttributeKeys(node.attributes);
|
|
789
|
+
fieldType = this.inputTypeResolver(cleanFieldName, attributeKeys);
|
|
790
|
+
} else {
|
|
791
|
+
fieldType = this.inferInputType(cleanFieldName);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (fieldType === 'dynamicSingleSelect') {
|
|
796
|
+
this.buildDynamicSingleSelect(node, rawFieldName);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const fieldSchema = [];
|
|
801
|
+
const fieldLabel = this.toTitleCase(cleanFieldName);
|
|
802
|
+
|
|
803
|
+
fieldSchema.push(fieldType, cleanFieldName, fieldLabel);
|
|
804
|
+
|
|
805
|
+
let validations = {};
|
|
806
|
+
let attributes = {};
|
|
807
|
+
|
|
808
|
+
let inputParams;
|
|
809
|
+
if (node.attributes.length > 0) {
|
|
810
|
+
inputParams = this.handleAttributes(node.attributes);
|
|
811
|
+
} else {
|
|
812
|
+
inputParams = { validations: {}, attributes: {} };
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
validations = inputParams.validations;
|
|
816
|
+
attributes = inputParams.attributes;
|
|
817
|
+
|
|
818
|
+
if (this.isRequired(rawFieldName)) {
|
|
819
|
+
validations['required'] = true;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
fieldSchema.push(validations);
|
|
823
|
+
fieldSchema.push(attributes);
|
|
824
|
+
|
|
825
|
+
// Handle options for select, radio, checkbox fields
|
|
826
|
+
if (node.attributes.length > 0 && (fieldType === 'checkbox' || fieldType === 'radio' || fieldType === 'select' || fieldType === 'multipleSelect')) {
|
|
827
|
+
const getDefaultValue = (attributes) => {
|
|
828
|
+
console.log("AST", JSON.stringify(this.ast,null,2))
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
if (!Array.isArray(attributes)) {
|
|
832
|
+
console.debug("getDefaultValue: Input is not an array, returning null.");
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Look for FieldAttributes with 'selected' or 'default'
|
|
837
|
+
const selectedAttr = attributes.find(attr =>
|
|
838
|
+
attr?.type === "FieldAttribute" &&
|
|
839
|
+
(attr?.key === "selected" || attr?.key === "default")
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
// Look for OptionsAttributes with 'selected' or 'default'
|
|
843
|
+
const selectedOptionsAttr = attributes.find(attr =>
|
|
844
|
+
attr?.type === "OptionsAttribute" &&
|
|
845
|
+
(attr?.key === "selected" || attr?.key === "default")
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
console.debug("getDefaultValue: Found FieldAttribute:", selectedAttr);
|
|
849
|
+
console.debug("getDefaultValue: Found OptionsAttribute:", selectedOptionsAttr);
|
|
850
|
+
|
|
851
|
+
if (selectedAttr) {
|
|
852
|
+
// ... (existing logic)
|
|
853
|
+
// The previous logic was simplified, here is a more explicit check
|
|
854
|
+
const value = selectedAttr.value?.value?.toLowerCase() || selectedAttr.value?.toLowerCase();
|
|
855
|
+
console.debug(`getDefaultValue: Returning value from FieldAttribute: ${value}`);
|
|
856
|
+
return value;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (selectedOptionsAttr && selectedOptionsAttr.values && selectedOptionsAttr.values.length > 0) {
|
|
860
|
+
const value = selectedOptionsAttr.values[0].value.toLowerCase();
|
|
861
|
+
console.debug(`getDefaultValue: Returning value from OptionsAttribute: ${value}`);
|
|
862
|
+
return value;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
console.debug("getDefaultValue: No matching attribute found, returning null.");
|
|
866
|
+
return null;
|
|
867
|
+
};
|
|
868
|
+
const defaultValue = getDefaultValue(node.attributes);
|
|
869
|
+
console.log("defaultValue",defaultValue);
|
|
870
|
+
const optionValues = this.extractOptionValues(node.attributes);
|
|
871
|
+
let options = [];
|
|
872
|
+
|
|
873
|
+
if (optionValues.length > 0) {
|
|
874
|
+
optionValues.forEach(option => {
|
|
875
|
+
if (option.toLowerCase() === defaultValue) {
|
|
876
|
+
options.push({value: option.toLowerCase(), label: this.toTitleCase(option), selected: true});
|
|
877
|
+
} else {
|
|
878
|
+
options.push({value: option.toLowerCase(), label: this.toTitleCase(option)});
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
fieldSchema.push(options);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
this.formSchema.push(fieldSchema);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
/*
|
|
890
|
+
buildCheckboxField(node, rawFieldName) {
|
|
891
|
+
const cleanString = this.cleanFieldName(rawFieldName);
|
|
892
|
+
const fieldName = cleanString.input_name;
|
|
893
|
+
const fieldSchema = ['checkbox', fieldName, this.toTitleCase(fieldName)];
|
|
894
|
+
|
|
895
|
+
let validations = {};
|
|
896
|
+
let attributes = {};
|
|
897
|
+
|
|
898
|
+
// Extract options from all OptionsAttributes
|
|
899
|
+
const allOptions = [];
|
|
900
|
+
|
|
901
|
+
node.attributes.forEach(attr => {
|
|
902
|
+
if (attr.type === 'OptionsAttribute') {
|
|
903
|
+
const optionValues = attr.values.map(option => option.value);
|
|
904
|
+
allOptions.push(...optionValues);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// Build options array
|
|
909
|
+
const options = allOptions.map(option => ({
|
|
910
|
+
value: option.toLowerCase(),
|
|
911
|
+
label: this.toTitleCase(option)
|
|
912
|
+
}));
|
|
913
|
+
|
|
914
|
+
// Handle validations and attributes
|
|
915
|
+
if (node.attributes.length > 0) {
|
|
916
|
+
const inputParams = this.handleAttributes(node.attributes);
|
|
917
|
+
validations = inputParams.validations;
|
|
918
|
+
attributes = inputParams.attributes;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (this.isRequired(rawFieldName)) {
|
|
922
|
+
validations['required'] = true;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
fieldSchema.push(validations);
|
|
926
|
+
fieldSchema.push(attributes);
|
|
927
|
+
fieldSchema.push(options);
|
|
928
|
+
|
|
929
|
+
this.formSchema.push(fieldSchema);
|
|
930
|
+
}
|
|
931
|
+
*/
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
handleAttributes(attributesAST) {
|
|
937
|
+
let validations = {};
|
|
938
|
+
let attributes = {};
|
|
939
|
+
|
|
940
|
+
attributesAST.forEach(attr => {
|
|
941
|
+
if (attr.type === 'FieldAttribute') {
|
|
942
|
+
const key = attr.key;
|
|
943
|
+
let value;
|
|
944
|
+
|
|
945
|
+
if (typeof attr.value === 'object') {
|
|
946
|
+
value = attr.value.value;
|
|
947
|
+
} else {
|
|
948
|
+
value = attr.value;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Skip ignored keys (including selected/default which are handled separately)
|
|
952
|
+
if (this.ignoreAttributes.includes(key) || key === 'selected' || key === 'default') return;
|
|
953
|
+
|
|
954
|
+
// Categorize key as input attribute or validation
|
|
955
|
+
if (this.inputAttributes.includes(key)) {
|
|
956
|
+
attributes[key] = value;
|
|
957
|
+
} else if (this.validationAttributes.includes(key)) {
|
|
958
|
+
validations[key] = value;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// Handle dependents extraction
|
|
964
|
+
const getDependents = (attributesAST = []) => {
|
|
965
|
+
const dependentsAttr = attributesAST.find(attr => attr.key === "dependents");
|
|
966
|
+
if (!dependentsAttr) return [];
|
|
967
|
+
|
|
968
|
+
if (dependentsAttr.type === "OptionsAttribute" && dependentsAttr.values) {
|
|
969
|
+
return dependentsAttr.values
|
|
970
|
+
.map(option => option.value)
|
|
971
|
+
.filter(Boolean);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (dependentsAttr.value !== undefined) {
|
|
975
|
+
if (Array.isArray(dependentsAttr.value)) {
|
|
976
|
+
return dependentsAttr.value.filter(Boolean);
|
|
977
|
+
}
|
|
978
|
+
const values = typeof dependentsAttr.value === 'string'
|
|
979
|
+
? dependentsAttr.value.split(',').map(v => v.trim())
|
|
980
|
+
: [dependentsAttr.value];
|
|
981
|
+
return values.filter(Boolean);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return [];
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
let dependents = getDependents(attributesAST);
|
|
988
|
+
if (dependents.length > 0) {
|
|
989
|
+
attributes['dependents'] = dependents;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const getDependsOn = (attributesAST) => {
|
|
993
|
+
if (!Array.isArray(attributesAST)) return null;
|
|
994
|
+
|
|
995
|
+
const dependsOnAttr = attributesAST.find(
|
|
996
|
+
attr => attr?.type === "OptionsAttribute" && attr?.key === "dependsOn"
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
if (!dependsOnAttr?.values || dependsOnAttr.values.length < 2) {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return {
|
|
1004
|
+
dependsOnValue: dependsOnAttr.values[0]?.value || '',
|
|
1005
|
+
dependsOnCondition: dependsOnAttr.values[1]?.value || ''
|
|
1006
|
+
};
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
const dependency = getDependsOn(attributesAST);
|
|
1010
|
+
if (dependency) {
|
|
1011
|
+
const dependsOnCondition = dependency.dependsOnCondition.toLowerCase();
|
|
1012
|
+
attributes['dependsOn'] = dependency.dependsOnValue;
|
|
1013
|
+
attributes['condition'] = `${dependsOnCondition}`;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return {
|
|
1017
|
+
validations,
|
|
1018
|
+
attributes
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
buildOptionsAttribute(node) {
|
|
1025
|
+
//console.log(`Processing OptionsAttribute with ${node.values.length} values`);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
buildFieldAttribute(node) {
|
|
1029
|
+
//console.log(`Processing FieldAttribute: ${node.key} = ${node.value}`);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
buildOption(node) {
|
|
1033
|
+
//console.log(`Processing Option: ${node.value} (quoted: ${node.quoted})`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
buildIdentifier(node) {
|
|
1037
|
+
// console.log(`Processing Identifier: ${node.value}`);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
buildStringLiteral(node) {
|
|
1041
|
+
// console.log(`Processing StringLiteral: "${node.value}"`);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
addSubmit () {
|
|
1046
|
+
|
|
1047
|
+
this.formSchema.push(['submit','submit','Submit']);
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// class wrapper - nothing below this point
|
|
1053
|
+
|
|
1054
|
+
}
|