n8n-nodes-data-mapper 0.4.0 → 0.4.2

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.
@@ -7,80 +7,7 @@ const profile_1 = require("./profile");
7
7
  const visual_1 = require("./visual");
8
8
  const xml_1 = require("./xml");
9
9
  const show = (values) => ({ show: values });
10
- const mappingFields = [
11
- {
12
- displayName: 'Source',
13
- name: 'sourceType',
14
- type: 'options',
15
- options: [
16
- { name: 'Input Field', value: 'field', description: 'Read a value from the input by path' },
17
- {
18
- name: 'From Source Profile',
19
- value: 'profile',
20
- description: 'Pick a field from the source profile',
21
- },
22
- { name: 'Fixed Value', value: 'value', description: 'Write a fixed value or the result of an expression' },
23
- ],
24
- default: 'field',
25
- },
26
- {
27
- displayName: 'Source Field Name or ID',
28
- name: 'sourceField',
29
- type: 'options',
30
- typeOptions: { loadOptionsMethod: 'getSourceFields' },
31
- default: '',
32
- description: 'Field from the source profile. Fill the Source Profile field above first. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
33
- displayOptions: show({ sourceType: ['profile'] }),
34
- },
35
- {
36
- displayName: 'Source Path',
37
- name: 'source',
38
- type: 'string',
39
- default: '',
40
- placeholder: 'customer.addresses[*].city',
41
- description: 'Dot path into the input. Use [0] for one array element, [*] for every element, or leave empty for the whole input.',
42
- displayOptions: show({ sourceType: ['field'] }),
43
- },
44
- {
45
- displayName: 'Value',
46
- name: 'value',
47
- type: 'string',
48
- default: '',
49
- description: 'Fixed text, or an expression such as {{ $now.toISO() }}',
50
- displayOptions: show({ sourceType: ['value'] }),
51
- },
52
- {
53
- displayName: 'Target',
54
- name: 'targetMode',
55
- type: 'options',
56
- options: [
57
- { name: 'Custom Path', value: 'custom', description: 'Type the target path yourself' },
58
- {
59
- name: 'From Target Profile',
60
- value: 'profile',
61
- description: 'Pick a field from the uploaded target profile',
62
- },
63
- ],
64
- default: 'custom',
65
- },
66
- {
67
- displayName: 'Target Field Name or ID',
68
- name: 'targetField',
69
- type: 'options',
70
- typeOptions: { loadOptionsMethod: 'getTargetFields' },
71
- default: '',
72
- description: 'Field from the target profile. Fill the Target Profile field above first. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
73
- displayOptions: show({ targetMode: ['profile'] }),
74
- },
75
- {
76
- displayName: 'Target Path',
77
- name: 'target',
78
- type: 'string',
79
- default: '',
80
- placeholder: 'shipping.cities[*]',
81
- description: 'Where to write the value in the output. Each [*] matches a [*] in the source path, in order.',
82
- displayOptions: show({ targetMode: ['custom'] }),
83
- },
10
+ const transformFields = [
84
11
  {
85
12
  displayName: 'Transform',
86
13
  name: 'transform',
@@ -165,6 +92,82 @@ const mappingFields = [
165
92
  default: '',
166
93
  displayOptions: show({ transform: ['lookup'], lookupFallback: ['default'] }),
167
94
  },
95
+ ];
96
+ const mappingFields = [
97
+ {
98
+ displayName: 'Source',
99
+ name: 'sourceType',
100
+ type: 'options',
101
+ options: [
102
+ { name: 'Input Field', value: 'field', description: 'Read a value from the input by path' },
103
+ {
104
+ name: 'From Source Profile',
105
+ value: 'profile',
106
+ description: 'Pick a field from the source profile',
107
+ },
108
+ { name: 'Fixed Value', value: 'value', description: 'Write a fixed value or the result of an expression' },
109
+ ],
110
+ default: 'field',
111
+ },
112
+ {
113
+ displayName: 'Source Field Name or ID',
114
+ name: 'sourceField',
115
+ type: 'options',
116
+ typeOptions: { loadOptionsMethod: 'getSourceFields' },
117
+ default: '',
118
+ description: 'Field from the source profile. Fill the Source Profile field above first. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
119
+ displayOptions: show({ sourceType: ['profile'] }),
120
+ },
121
+ {
122
+ displayName: 'Source Path',
123
+ name: 'source',
124
+ type: 'string',
125
+ default: '',
126
+ placeholder: 'customer.addresses[*].city',
127
+ description: 'Dot path into the input. Use [0] for one array element, [*] for every element, or leave empty for the whole input.',
128
+ displayOptions: show({ sourceType: ['field'] }),
129
+ },
130
+ {
131
+ displayName: 'Value',
132
+ name: 'value',
133
+ type: 'string',
134
+ default: '',
135
+ description: 'Fixed text, or an expression such as {{ $now.toISO() }}',
136
+ displayOptions: show({ sourceType: ['value'] }),
137
+ },
138
+ {
139
+ displayName: 'Target',
140
+ name: 'targetMode',
141
+ type: 'options',
142
+ options: [
143
+ { name: 'Custom Path', value: 'custom', description: 'Type the target path yourself' },
144
+ {
145
+ name: 'From Target Profile',
146
+ value: 'profile',
147
+ description: 'Pick a field from the target profile',
148
+ },
149
+ ],
150
+ default: 'profile',
151
+ },
152
+ {
153
+ displayName: 'Target Field Name or ID',
154
+ name: 'targetField',
155
+ type: 'options',
156
+ typeOptions: { loadOptionsMethod: 'getTargetFields' },
157
+ default: '',
158
+ description: 'Field from the target profile. Fill the Target Profile field above first. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
159
+ displayOptions: show({ targetMode: ['profile'] }),
160
+ },
161
+ {
162
+ displayName: 'Target Path',
163
+ name: 'target',
164
+ type: 'string',
165
+ default: '',
166
+ placeholder: 'shipping.cities[*]',
167
+ description: 'Where to write the value in the output. Each [*] matches a [*] in the source path, in order.',
168
+ displayOptions: show({ targetMode: ['custom'] }),
169
+ },
170
+ ...transformFields,
168
171
  {
169
172
  displayName: 'If Source Is Missing',
170
173
  name: 'ifMissing',
@@ -239,26 +242,6 @@ const v1Properties = [
239
242
  description: 'XML is read with attributes under "$" and text under "_", so a path looks like order.$.ID or order.total._',
240
243
  displayOptions: show({ inputSource: ['field', 'binary'] }),
241
244
  },
242
- {
243
- displayName: 'Define Mappings',
244
- name: 'definitionMode',
245
- type: 'options',
246
- noDataExpression: true,
247
- options: [
248
- { name: 'Using Fields Below', value: 'fields' },
249
- {
250
- name: 'Auto-Map From Profile',
251
- value: 'auto',
252
- description: 'Match source fields to a target profile automatically by name',
253
- },
254
- {
255
- name: 'Using JSON',
256
- value: 'json',
257
- description: 'Paste or reference a mapping definition, so one mapping can be reused across workflows',
258
- },
259
- ],
260
- default: 'fields',
261
- },
262
245
  {
263
246
  displayName: 'Source Profile',
264
247
  name: 'sourceProfile',
@@ -348,6 +331,91 @@ const v1Properties = [
348
331
  description: 'The kind of profile you provided. Detect Automatically works for most profiles.',
349
332
  displayOptions: show({ definitionMode: ['fields', 'auto'] }),
350
333
  },
334
+ {
335
+ displayName: 'Define Mappings',
336
+ name: 'definitionMode',
337
+ type: 'options',
338
+ noDataExpression: true,
339
+ options: [
340
+ { name: 'Map Each Column Manually', value: 'fields', description: 'Add a mapping per target field' },
341
+ {
342
+ name: 'Auto-Map From Profile',
343
+ value: 'auto',
344
+ description: 'Match source fields to a target profile automatically by name',
345
+ },
346
+ {
347
+ name: 'Using JSON',
348
+ value: 'json',
349
+ description: 'Paste or reference a mapping definition, so one mapping can be reused across workflows',
350
+ },
351
+ ],
352
+ default: 'fields',
353
+ },
354
+ {
355
+ displayName: 'Mapping Layout',
356
+ name: 'mappingLayout',
357
+ type: 'options',
358
+ noDataExpression: true,
359
+ options: [
360
+ { name: 'One Mapping at a Time', value: 'list', description: 'Add a mapping row for each field' },
361
+ {
362
+ name: 'Fill Target Fields',
363
+ value: 'fields',
364
+ description: 'List every target-profile field and set the source for each, like a visual mapper',
365
+ },
366
+ ],
367
+ default: 'list',
368
+ displayOptions: show({ definitionMode: ['fields'] }),
369
+ },
370
+ {
371
+ displayName: 'Target Fields',
372
+ name: 'columnMapping',
373
+ type: 'resourceMapper',
374
+ noDataExpression: true,
375
+ default: { mappingMode: 'defineBelow', value: null },
376
+ required: true,
377
+ typeOptions: {
378
+ loadOptionsDependsOn: ['targetProfile', 'targetProfileFormat', 'sourceProfile', 'sourceProfileFormat'],
379
+ resourceMapper: {
380
+ resourceMapperMethod: 'getProfileFields',
381
+ mode: 'add',
382
+ valuesLabel: 'Target Fields',
383
+ fieldWords: { singular: 'target field', plural: 'target fields' },
384
+ addAllFields: true,
385
+ multiKeyMatch: false,
386
+ supportAutoMap: false,
387
+ noFieldsError: 'No target fields found. Paste a target profile above.',
388
+ },
389
+ },
390
+ displayOptions: show({ definitionMode: ['fields'], mappingLayout: ['fields'] }),
391
+ },
392
+ {
393
+ displayName: 'Transform a Field',
394
+ name: 'fieldTransforms',
395
+ placeholder: 'Add Field Transform',
396
+ type: 'fixedCollection',
397
+ typeOptions: { multipleValues: true },
398
+ default: {},
399
+ description: 'Convert or reformat the target fields above (trim, number, date, lookup, split/join, ...). Pick a field and its transform. Number and boolean types from the profile already convert on their own.',
400
+ displayOptions: show({ definitionMode: ['fields'], mappingLayout: ['fields'] }),
401
+ options: [
402
+ {
403
+ displayName: 'Field',
404
+ name: 'item',
405
+ values: [
406
+ {
407
+ displayName: 'Target Field Name or ID',
408
+ name: 'targetField',
409
+ type: 'options',
410
+ typeOptions: { loadOptionsMethod: 'getTargetFields' },
411
+ default: '',
412
+ description: 'Which target field to transform. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
413
+ },
414
+ ...transformFields,
415
+ ],
416
+ },
417
+ ],
418
+ },
351
419
  {
352
420
  displayName: 'Mappings',
353
421
  name: 'mappings',
@@ -356,7 +424,7 @@ const v1Properties = [
356
424
  typeOptions: { multipleValues: true, sortable: true },
357
425
  default: {},
358
426
  description: 'Applied in order; a later mapping to the same target overwrites an earlier one',
359
- displayOptions: show({ definitionMode: ['fields'] }),
427
+ displayOptions: show({ definitionMode: ['fields'], mappingLayout: ['list'] }),
360
428
  options: [{ displayName: 'Mapping', name: 'mapping', values: mappingFields }],
361
429
  },
362
430
  {
@@ -365,7 +433,7 @@ const v1Properties = [
365
433
  type: 'boolean',
366
434
  default: false,
367
435
  description: 'Whether to also fill any target-profile fields you have not mapped above, by matching them to source fields by name',
368
- displayOptions: show({ definitionMode: ['fields'] }),
436
+ displayOptions: show({ definitionMode: ['fields'], mappingLayout: ['list'] }),
369
437
  },
370
438
  {
371
439
  displayName: 'Mapping Definition',
@@ -381,7 +449,7 @@ const v1Properties = [
381
449
  type: 'collection',
382
450
  placeholder: 'Add option',
383
451
  default: {},
384
- displayOptions: show({ definitionMode: ['auto'] }),
452
+ displayOptions: show({ definitionMode: ['fields', 'auto'] }),
385
453
  options: [
386
454
  {
387
455
  displayName: 'Add Auto-Map Report',
@@ -397,6 +465,15 @@ const v1Properties = [
397
465
  default: false,
398
466
  description: 'Whether field names must match in the same letter case',
399
467
  },
468
+ {
469
+ displayName: 'Custom Synonyms',
470
+ name: 'synonyms',
471
+ type: 'string',
472
+ typeOptions: { rows: 3 },
473
+ default: '',
474
+ placeholder: 'menge=quantity\nartikel=article',
475
+ description: 'Extra name matches for auto-map, one source=target per line (or a JSON object). Qty=quantity, Amt=amount and other common abbreviations are already built in.',
476
+ },
400
477
  {
401
478
  displayName: 'Match',
402
479
  name: 'strategy',
@@ -454,6 +531,8 @@ const v1Properties = [
454
531
  type: 'options',
455
532
  noDataExpression: true,
456
533
  options: [
534
+ { name: 'CSV', value: 'csv', description: 'Output the mapped data as CSV text in a field' },
535
+ { name: 'CSV File', value: 'csvFile', description: 'Output the mapped data as a CSV file' },
457
536
  { name: 'JSON', value: 'json', description: 'Output the mapped data as the item' },
458
537
  { name: 'JSON File', value: 'jsonFile', description: 'Output the mapped data as a JSON file' },
459
538
  { name: 'XML', value: 'xml', description: 'Output the mapped data as XML text in a field' },
@@ -465,9 +544,10 @@ const v1Properties = [
465
544
  displayName: 'Put Output in Field',
466
545
  name: 'outputField',
467
546
  type: 'string',
468
- default: 'xml',
469
- required: true,
470
- displayOptions: show({ outputFormat: ['xml'] }),
547
+ default: '',
548
+ placeholder: 'xml',
549
+ description: 'Field for the XML or CSV text. Leave empty to use "xml" or "csv".',
550
+ displayOptions: show({ outputFormat: ['xml', 'csv'] }),
471
551
  },
472
552
  {
473
553
  displayName: 'Put Output File in Field',
@@ -475,7 +555,7 @@ const v1Properties = [
475
555
  type: 'string',
476
556
  default: 'data',
477
557
  required: true,
478
- displayOptions: show({ outputFormat: ['xmlFile', 'jsonFile'] }),
558
+ displayOptions: show({ outputFormat: ['xmlFile', 'jsonFile', 'csvFile'] }),
479
559
  },
480
560
  {
481
561
  displayName: 'File Name',
@@ -484,7 +564,7 @@ const v1Properties = [
484
564
  default: '',
485
565
  placeholder: 'output.xml',
486
566
  description: 'Leave empty to name the file output with the matching extension',
487
- displayOptions: show({ outputFormat: ['xmlFile', 'jsonFile'] }),
567
+ displayOptions: show({ outputFormat: ['xmlFile', 'jsonFile', 'csvFile'] }),
488
568
  },
489
569
  {
490
570
  displayName: 'Root Element',
@@ -503,6 +583,72 @@ const v1Properties = [
503
583
  description: 'Whether to start the XML with &lt;?xml version="1.0" encoding="UTF-8"?&gt;',
504
584
  displayOptions: show({ outputFormat: ['xml', 'xmlFile'] }),
505
585
  },
586
+ {
587
+ displayName: 'CSV Options',
588
+ name: 'csvOptions',
589
+ type: 'collection',
590
+ placeholder: 'Add option',
591
+ default: {},
592
+ displayOptions: show({ outputFormat: ['csv', 'csvFile'] }),
593
+ options: [
594
+ { displayName: 'Delimiter', name: 'csvDelimiter', type: 'string', default: ',' },
595
+ {
596
+ displayName: 'Escape Formulas',
597
+ name: 'csvEscapeFormulas',
598
+ type: 'boolean',
599
+ default: false,
600
+ description: "Whether to put ' before values starting with =, +, -, @, tab or CR so spreadsheets don't run them as formulas (CSV injection)",
601
+ },
602
+ {
603
+ displayName: 'Header Row',
604
+ name: 'csvHeader',
605
+ type: 'boolean',
606
+ default: true,
607
+ description: 'Whether to start the CSV with a row of column names',
608
+ },
609
+ {
610
+ displayName: 'Line Ending',
611
+ name: 'csvLineEnding',
612
+ type: 'options',
613
+ options: [
614
+ { name: 'LF', value: 'lf' },
615
+ { name: 'CRLF', value: 'crlf' },
616
+ ],
617
+ default: 'lf',
618
+ },
619
+ {
620
+ displayName: 'One CSV for All Items',
621
+ name: 'csvCombine',
622
+ type: 'boolean',
623
+ default: true,
624
+ description: 'Whether to put every item into one CSV document, instead of one CSV per item',
625
+ },
626
+ ],
627
+ },
628
+ {
629
+ displayName: 'Options',
630
+ name: 'fieldOptions',
631
+ type: 'collection',
632
+ placeholder: 'Add option',
633
+ default: {},
634
+ displayOptions: show({ definitionMode: ['fields'], mappingLayout: ['fields'] }),
635
+ options: [
636
+ {
637
+ displayName: 'Convert Types From Profile',
638
+ name: 'convertTypes',
639
+ type: 'boolean',
640
+ default: true,
641
+ description: 'Whether to turn values into numbers or booleans where the profile says so. Values that cannot be converted are kept as they are.',
642
+ },
643
+ {
644
+ displayName: 'Keep Empty Values',
645
+ name: 'keepEmpty',
646
+ type: 'boolean',
647
+ default: false,
648
+ description: 'Whether to write target fields whose value is empty, instead of leaving them out',
649
+ },
650
+ ],
651
+ },
506
652
  ];
507
653
  function forVersions(properties, versions) {
508
654
  return properties.map((property) => {
@@ -524,9 +670,9 @@ class DataMapper {
524
670
  icon: { light: 'file:mapper.svg', dark: 'file:mapper.dark.svg' },
525
671
  group: ['transform'],
526
672
  version: [1, 2],
527
- defaultVersion: 2,
673
+ defaultVersion: 1,
528
674
  subtitle: '={{ $parameter["targetFormat"] ? "Map to " + $parameter["targetFormat"].toUpperCase() : $parameter["definitionMode"] === "auto" ? "Auto-map from profile" : $parameter["definitionMode"] === "json" ? "JSON definition" : "Field mappings" }}',
529
- description: 'Drag and drop incoming data onto a target profile and output XML, JSON or CSV',
675
+ description: 'Map and transform data between structures and output XML, JSON or CSV',
530
676
  defaults: {
531
677
  name: 'Data Mapper',
532
678
  },
@@ -599,18 +745,30 @@ class DataMapper {
599
745
  const items = this.getInputData();
600
746
  const returnData = [];
601
747
  const profileCache = new Map();
748
+ const format0 = this.getNodeParameter('outputFormat', 0, 'json');
749
+ const combineCsv = (format0 === 'csv' || format0 === 'csvFile') &&
750
+ this.getNodeParameter('csvOptions.csvCombine', 0, true) !== false;
751
+ const csvRecords = [];
752
+ let csvProfile;
602
753
  for (let i = 0; i < items.length; i++) {
603
754
  try {
604
755
  const input = await readInput(this, items[i], i);
605
- const { rules, report } = await buildRules(this, i, input, profileCache);
756
+ const { rules, report, profile, order } = await buildRules(this, i, input, profileCache);
606
757
  const unmapped = this.getNodeParameter('unmapped', i);
607
758
  const start = unmapped === 'keep' && input && typeof input === 'object' && !Array.isArray(input)
608
759
  ? structuredClone(input)
609
760
  : {};
610
- const mapped = (0, mapping_1.applyMappings)(input, rules, start);
761
+ let mapped = (0, mapping_1.applyMappings)(input, rules, start);
762
+ if (order && profile)
763
+ mapped = (0, visual_1.orderByProfile)(mapped, profile);
611
764
  if (report)
612
765
  mapped._autoMap = report;
613
- returnData.push(await writeOutput(this, mapped, i));
766
+ if (combineCsv) {
767
+ csvRecords.push({ mapped, index: i });
768
+ csvProfile !== null && csvProfile !== void 0 ? csvProfile : (csvProfile = profile);
769
+ continue;
770
+ }
771
+ returnData.push(await writeOutput(this, mapped, i, profile));
614
772
  }
615
773
  catch (error) {
616
774
  if (this.continueOnFail()) {
@@ -629,6 +787,10 @@ class DataMapper {
629
787
  });
630
788
  }
631
789
  }
790
+ if (combineCsv && csvRecords.length > 0) {
791
+ const output = await csvOutput(this, csvRecords.map((r) => r.mapped), csvProfile, 0, csvRecords.map((r) => ({ item: r.index })));
792
+ returnData.push(output);
793
+ }
632
794
  return [returnData];
633
795
  }
634
796
  }
@@ -713,6 +875,7 @@ function readAutoOptions(ctx, itemIndex) {
713
875
  caseSensitive: o.caseSensitive === true,
714
876
  reuseSources: o.reuseSources === true,
715
877
  unmatched: (_c = o.unmatched) !== null && _c !== void 0 ? _c : 'skip',
878
+ synonyms: (0, profile_1.parseSynonyms)(o.synonyms),
716
879
  };
717
880
  }
718
881
  function uiMappingToRule(m, ctx, itemIndex) {
@@ -750,7 +913,28 @@ function uiMappingToRule(m, ctx, itemIndex) {
750
913
  default: m.defaultValue,
751
914
  };
752
915
  }
916
+ function applyFieldTransforms(ctx, itemIndex, rules) {
917
+ const items = ctx.getNodeParameter('fieldTransforms.item', itemIndex, []).filter(Boolean);
918
+ for (const it of items) {
919
+ if (!it.targetField || !it.transform || it.transform === 'none')
920
+ continue;
921
+ for (const rule of rules) {
922
+ if (rule.target !== it.targetField)
923
+ continue;
924
+ rule.transform = it.transform;
925
+ rule.separator = it.separator;
926
+ rule.decimals = it.decimals;
927
+ rule.dateInputFormat = it.dateInputFormat;
928
+ rule.dateOutputFormat = it.dateOutputFormat;
929
+ rule.lookup = it.lookupTable;
930
+ rule.lookupFallback = it.lookupFallback;
931
+ rule.lookupDefault = it.lookupDefault;
932
+ rule.lenient = true;
933
+ }
934
+ }
935
+ }
753
936
  async function buildRules(ctx, itemIndex, input, cache) {
937
+ var _a, _b;
754
938
  const mode = ctx.getNodeParameter('definitionMode', itemIndex);
755
939
  if (mode === 'json')
756
940
  return { rules: (0, mapping_1.parseDefinition)(ctx.getNodeParameter('mappingJson', itemIndex)) };
@@ -770,27 +954,58 @@ async function buildRules(ctx, itemIndex, input, cache) {
770
954
  }
771
955
  return built;
772
956
  }
957
+ if (ctx.getNodeParameter('mappingLayout', itemIndex, 'list') === 'fields') {
958
+ const raw = ((_a = ctx.getNodeParameter('columnMapping.value', itemIndex, {}, {
959
+ rawExpressions: true,
960
+ skipValidation: true,
961
+ })) !== null && _a !== void 0 ? _a : {});
962
+ const rm = ctx.getNodeParameter('columnMapping', itemIndex, undefined, { skipValidation: true });
963
+ const resolved = ((_b = rm === null || rm === void 0 ? void 0 : rm.value) !== null && _b !== void 0 ? _b : {});
964
+ const fo = ctx.getNodeParameter('fieldOptions', itemIndex, {});
965
+ const rules = (0, visual_1.buildDropRules)(raw, resolved, profile, {
966
+ convertTypes: fo.convertTypes !== false,
967
+ keepEmpty: fo.keepEmpty === true,
968
+ });
969
+ applyFieldTransforms(ctx, itemIndex, rules);
970
+ return { rules, profile, order: true };
971
+ }
773
972
  const entries = ctx.getNodeParameter('mappings.mapping', itemIndex, []).filter(Boolean);
774
973
  const rules = entries.map((m) => uiMappingToRule(m, ctx, itemIndex));
775
974
  if (profile && ctx.getNodeParameter('autoMapRemaining', itemIndex, false) === true) {
776
975
  const covered = new Set(rules.map((r) => r.target));
777
976
  const remaining = { ...profile, fields: profile.fields.filter((f) => !covered.has(f.path)) };
778
- const result = (0, profile_1.autoMap)((0, profile_1.flattenSource)(input), remaining, {
779
- threshold: 0.6,
780
- strategy: 'fuzzy',
781
- caseSensitive: false,
782
- reuseSources: false,
783
- unmatched: 'skip',
784
- });
977
+ const result = (0, profile_1.autoMap)((0, profile_1.flattenSource)(input), remaining, readAutoOptions(ctx, itemIndex));
785
978
  rules.push(...result.rules);
979
+ if (ctx.getNodeParameter('autoOptions.report', itemIndex, false) === true) {
980
+ return {
981
+ rules,
982
+ profile,
983
+ report: { matched: result.matched, unmatched: result.unmatched },
984
+ };
985
+ }
786
986
  }
787
987
  return { rules, profile };
788
988
  }
789
- async function writeOutput(ctx, mapped, itemIndex) {
989
+ async function csvOutput(ctx, records, profile, itemIndex, pairedItem) {
990
+ const o = ctx.getNodeParameter('csvOptions', itemIndex, {});
991
+ const text = (0, visual_1.csvText)(records, profile, o);
992
+ if (ctx.getNodeParameter('outputFormat', itemIndex) === 'csvFile') {
993
+ const field = ctx.getNodeParameter('outputBinaryField', itemIndex);
994
+ const fileName = ctx.getNodeParameter('fileName', itemIndex, '') || 'output.csv';
995
+ const binary = await ctx.helpers.prepareBinaryData(Buffer.from(text, 'utf8'), fileName, 'text/csv');
996
+ return { json: {}, binary: { [field]: binary }, pairedItem };
997
+ }
998
+ const field = ctx.getNodeParameter('outputField', itemIndex, '') || 'csv';
999
+ return { json: { [field]: text }, pairedItem };
1000
+ }
1001
+ async function writeOutput(ctx, mapped, itemIndex, profile) {
790
1002
  const format = ctx.getNodeParameter('outputFormat', itemIndex);
791
1003
  const pairedItem = { item: itemIndex };
792
1004
  if (format === 'json')
793
1005
  return { json: mapped, pairedItem };
1006
+ if (format === 'csv' || format === 'csvFile') {
1007
+ return csvOutput(ctx, [mapped], profile, itemIndex, [pairedItem]);
1008
+ }
794
1009
  if (format === 'jsonFile') {
795
1010
  const field = ctx.getNodeParameter('outputBinaryField', itemIndex);
796
1011
  const fileName = ctx.getNodeParameter('fileName', itemIndex, '') || 'output.json';
@@ -804,7 +1019,7 @@ async function writeOutput(ctx, mapped, itemIndex) {
804
1019
  textKey: '_',
805
1020
  });
806
1021
  if (format === 'xml') {
807
- const field = ctx.getNodeParameter('outputField', itemIndex);
1022
+ const field = ctx.getNodeParameter('outputField', itemIndex, '') || 'xml';
808
1023
  return { json: { [field]: xml }, pairedItem };
809
1024
  }
810
1025
  const field = ctx.getNodeParameter('outputBinaryField', itemIndex);