n8n-nodes-excel-ai 1.0.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.
@@ -0,0 +1,1111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ExcelAI = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const ExcelJS = __importStar(require("exceljs"));
29
+ const fs = __importStar(require("fs/promises"));
30
+ class ExcelAI {
31
+ constructor() {
32
+ this.description = {
33
+ displayName: 'Excel AI',
34
+ name: 'excelAI',
35
+ icon: 'file:excel.svg',
36
+ group: ['transform'],
37
+ version: 1,
38
+ subtitle: '={{$parameter["resource"] + ": " + ($parameter["resource"] === "row" ? $parameter["operation"] : $parameter["worksheetOperation"])}}',
39
+ description: 'Perform CRUD operations on Excel files with AI Agent support',
40
+ defaults: {
41
+ name: 'Excel AI',
42
+ },
43
+ inputs: ['main'],
44
+ outputs: ['main'],
45
+ // Enable AI Agent Integration
46
+ usableAsTool: true,
47
+ properties: [
48
+ // Resource Selection
49
+ {
50
+ displayName: 'Resource',
51
+ name: 'resource',
52
+ type: 'options',
53
+ noDataExpression: true,
54
+ options: [
55
+ {
56
+ name: 'Row',
57
+ value: 'row',
58
+ description: 'Perform operations on rows',
59
+ },
60
+ {
61
+ name: 'Worksheet',
62
+ value: 'worksheet',
63
+ description: 'Perform operations on worksheets',
64
+ },
65
+ ],
66
+ default: 'row',
67
+ },
68
+ // Input Mode
69
+ {
70
+ displayName: 'Input Mode',
71
+ name: 'inputMode',
72
+ type: 'options',
73
+ options: [
74
+ {
75
+ name: 'File Path',
76
+ value: 'filePath',
77
+ description: 'Use file from file system',
78
+ },
79
+ {
80
+ name: 'Binary Data',
81
+ value: 'binaryData',
82
+ description: 'Use file from binary data',
83
+ },
84
+ ],
85
+ default: 'filePath',
86
+ description: 'How to provide the Excel file',
87
+ },
88
+ // File Path Input (AI-friendly)
89
+ {
90
+ displayName: 'File Path',
91
+ name: 'filePath',
92
+ type: 'string',
93
+ displayOptions: {
94
+ show: {
95
+ inputMode: ['filePath'],
96
+ },
97
+ },
98
+ default: '',
99
+ required: true,
100
+ placeholder: '/data/myfile.xlsx',
101
+ description: 'Absolute path to the Excel file',
102
+ },
103
+ // Binary Property Name
104
+ {
105
+ displayName: 'Binary Property',
106
+ name: 'binaryPropertyName',
107
+ type: 'string',
108
+ displayOptions: {
109
+ show: {
110
+ inputMode: ['binaryData'],
111
+ },
112
+ },
113
+ default: 'data',
114
+ required: true,
115
+ description: 'Name of the binary property containing the Excel file',
116
+ },
117
+ // Row Operations
118
+ {
119
+ displayName: 'Operation',
120
+ name: 'operation',
121
+ type: 'options',
122
+ noDataExpression: true,
123
+ displayOptions: {
124
+ show: {
125
+ resource: ['row'],
126
+ },
127
+ },
128
+ options: [
129
+ {
130
+ name: 'Read Rows',
131
+ value: 'readRows',
132
+ description: 'Read data from Excel file',
133
+ action: 'Read rows from Excel',
134
+ },
135
+ {
136
+ name: 'Append Row',
137
+ value: 'appendRow',
138
+ description: 'Add a new row at the end',
139
+ action: 'Add row to Excel',
140
+ },
141
+ {
142
+ name: 'Insert Row',
143
+ value: 'insertRow',
144
+ description: 'Insert a row at specific position',
145
+ action: 'Insert row in Excel',
146
+ },
147
+ {
148
+ name: 'Update Row',
149
+ value: 'updateRow',
150
+ description: 'Update an existing row',
151
+ action: 'Update row in Excel',
152
+ },
153
+ {
154
+ name: 'Delete Row',
155
+ value: 'deleteRow',
156
+ description: 'Delete a specific row',
157
+ action: 'Delete row from Excel',
158
+ },
159
+ {
160
+ name: 'Find Rows',
161
+ value: 'findRows',
162
+ description: 'Search for rows matching criteria',
163
+ action: 'Find rows in Excel',
164
+ },
165
+ ],
166
+ default: 'readRows',
167
+ },
168
+ // Worksheet Operations
169
+ {
170
+ displayName: 'Worksheet Operation',
171
+ name: 'worksheetOperation',
172
+ type: 'options',
173
+ displayOptions: {
174
+ show: {
175
+ resource: ['worksheet'],
176
+ },
177
+ },
178
+ options: [
179
+ {
180
+ name: 'List Worksheets',
181
+ value: 'listWorksheets',
182
+ description: 'Get list of all worksheets',
183
+ action: 'List worksheets in Excel',
184
+ },
185
+ {
186
+ name: 'Create Worksheet',
187
+ value: 'createWorksheet',
188
+ description: 'Create a new worksheet',
189
+ action: 'Create worksheet in Excel',
190
+ },
191
+ {
192
+ name: 'Delete Worksheet',
193
+ value: 'deleteWorksheet',
194
+ description: 'Delete a worksheet',
195
+ action: 'Delete worksheet from Excel',
196
+ },
197
+ {
198
+ name: 'Rename Worksheet',
199
+ value: 'renameWorksheet',
200
+ description: 'Rename an existing worksheet',
201
+ action: 'Rename worksheet in Excel',
202
+ },
203
+ {
204
+ name: 'Copy Worksheet',
205
+ value: 'copyWorksheet',
206
+ description: 'Copy a worksheet to a new worksheet',
207
+ action: 'Copy worksheet in Excel',
208
+ },
209
+ {
210
+ name: 'Get Worksheet Info',
211
+ value: 'getWorksheetInfo',
212
+ description: 'Get detailed information about a worksheet including columns',
213
+ action: 'Get worksheet info from Excel',
214
+ },
215
+ ],
216
+ default: 'listWorksheets',
217
+ },
218
+ // Sheet Name Options for File Path Mode (Row Operations)
219
+ {
220
+ displayName: 'Sheet Name',
221
+ name: 'sheetNameOptions',
222
+ type: 'options',
223
+ displayOptions: {
224
+ show: {
225
+ inputMode: ['filePath'],
226
+ resource: ['row'],
227
+ },
228
+ },
229
+ typeOptions: {
230
+ loadOptionsMethod: 'getWorksheets',
231
+ loadOptionsDependsOn: ['filePath'],
232
+ },
233
+ default: '',
234
+ required: false,
235
+ description: 'Name of the worksheet to operate on. If not specified, the first worksheet will be used.',
236
+ },
237
+ // Sheet Name for Binary Mode
238
+ {
239
+ displayName: 'Sheet Name',
240
+ name: 'sheetName',
241
+ type: 'string',
242
+ displayOptions: {
243
+ show: {
244
+ inputMode: ['binaryData'],
245
+ resource: ['row'],
246
+ },
247
+ },
248
+ default: '',
249
+ required: false,
250
+ description: 'Name of the worksheet to operate on. If not specified, the first worksheet will be used.',
251
+ },
252
+ // Read Rows Parameters
253
+ {
254
+ displayName: 'Start Row',
255
+ name: 'startRow',
256
+ type: 'number',
257
+ displayOptions: {
258
+ show: {
259
+ resource: ['row'],
260
+ operation: ['readRows'],
261
+ },
262
+ },
263
+ default: 2,
264
+ description: 'Row number to start reading from (1-based, row 1 is header)',
265
+ },
266
+ {
267
+ displayName: 'End Row',
268
+ name: 'endRow',
269
+ type: 'number',
270
+ displayOptions: {
271
+ show: {
272
+ resource: ['row'],
273
+ operation: ['readRows'],
274
+ },
275
+ },
276
+ default: 0,
277
+ description: 'Row number to end reading (0 means read all rows)',
278
+ },
279
+ // Row Data (JSON) for Add/Insert
280
+ {
281
+ displayName: 'Row Data',
282
+ name: 'rowData',
283
+ type: 'json',
284
+ displayOptions: {
285
+ show: {
286
+ resource: ['row'],
287
+ operation: ['appendRow', 'insertRow'],
288
+ },
289
+ },
290
+ default: '{}',
291
+ required: true,
292
+ placeholder: '{"Name": "John Doe", "Age": 30, "Email": "john@example.com"}',
293
+ description: 'Row data as JSON object. Column names will be automatically mapped.',
294
+ },
295
+ // Row Number for Insert/Update/Delete
296
+ {
297
+ displayName: 'Row Number',
298
+ name: 'rowNumber',
299
+ type: 'number',
300
+ displayOptions: {
301
+ show: {
302
+ resource: ['row'],
303
+ operation: ['insertRow', 'updateRow', 'deleteRow'],
304
+ },
305
+ },
306
+ default: 2,
307
+ required: true,
308
+ description: 'Target row number (1-based, row 1 is header)',
309
+ },
310
+ // Updated Data for Update
311
+ {
312
+ displayName: 'Updated Data',
313
+ name: 'updatedData',
314
+ type: 'json',
315
+ displayOptions: {
316
+ show: {
317
+ resource: ['row'],
318
+ operation: ['updateRow'],
319
+ },
320
+ },
321
+ default: '{}',
322
+ required: true,
323
+ placeholder: '{"Email": "newemail@example.com", "Status": "Active"}',
324
+ description: 'Data to update as JSON object. Only specified columns will be updated.',
325
+ },
326
+ // Find Rows Parameters
327
+ {
328
+ displayName: 'Search Column',
329
+ name: 'searchColumn',
330
+ type: 'options',
331
+ typeOptions: {
332
+ loadOptionsMethod: 'getColumns',
333
+ loadOptionsDependsOn: ['filePath', 'sheetNameOptions'],
334
+ },
335
+ displayOptions: {
336
+ show: {
337
+ resource: ['row'],
338
+ operation: ['findRows'],
339
+ },
340
+ },
341
+ default: '',
342
+ required: true,
343
+ description: 'Column to search in',
344
+ },
345
+ {
346
+ displayName: 'Search Value',
347
+ name: 'searchValue',
348
+ type: 'string',
349
+ displayOptions: {
350
+ show: {
351
+ resource: ['row'],
352
+ operation: ['findRows'],
353
+ },
354
+ },
355
+ default: '',
356
+ required: true,
357
+ description: 'Value to search for',
358
+ },
359
+ {
360
+ displayName: 'Match Type',
361
+ name: 'matchType',
362
+ type: 'options',
363
+ options: [
364
+ {
365
+ name: 'Exact',
366
+ value: 'exact',
367
+ description: 'Exact match (case-insensitive)',
368
+ },
369
+ {
370
+ name: 'Contains',
371
+ value: 'contains',
372
+ description: 'Contains substring (case-insensitive)',
373
+ },
374
+ {
375
+ name: 'Starts With',
376
+ value: 'startsWith',
377
+ description: 'Starts with (case-insensitive)',
378
+ },
379
+ {
380
+ name: 'Ends With',
381
+ value: 'endsWith',
382
+ description: 'Ends with (case-insensitive)',
383
+ },
384
+ ],
385
+ displayOptions: {
386
+ show: {
387
+ resource: ['row'],
388
+ operation: ['findRows'],
389
+ },
390
+ },
391
+ default: 'exact',
392
+ description: 'How to match the search value',
393
+ },
394
+ {
395
+ displayName: 'Return Row Numbers Only',
396
+ name: 'returnRowNumbers',
397
+ type: 'boolean',
398
+ displayOptions: {
399
+ show: {
400
+ resource: ['row'],
401
+ operation: ['findRows'],
402
+ },
403
+ },
404
+ default: false,
405
+ description: 'Whether to return only row numbers instead of full row data',
406
+ },
407
+ // Worksheet Parameters
408
+ {
409
+ displayName: 'Worksheet Name',
410
+ name: 'worksheetNameOptions',
411
+ type: 'options',
412
+ typeOptions: {
413
+ loadOptionsMethod: 'getWorksheets',
414
+ loadOptionsDependsOn: ['filePath'],
415
+ },
416
+ displayOptions: {
417
+ show: {
418
+ resource: ['worksheet'],
419
+ worksheetOperation: ['deleteWorksheet', 'renameWorksheet', 'copyWorksheet', 'getWorksheetInfo'],
420
+ inputMode: ['filePath'],
421
+ },
422
+ },
423
+ default: '',
424
+ required: true,
425
+ description: 'Name of the worksheet',
426
+ },
427
+ {
428
+ displayName: 'New Sheet Name',
429
+ name: 'newSheetName',
430
+ type: 'string',
431
+ displayOptions: {
432
+ show: {
433
+ resource: ['worksheet'],
434
+ worksheetOperation: ['createWorksheet', 'renameWorksheet', 'copyWorksheet'],
435
+ },
436
+ },
437
+ default: '',
438
+ required: true,
439
+ placeholder: 'NewSheet',
440
+ description: 'Name for the new worksheet',
441
+ },
442
+ {
443
+ displayName: 'Initial Data',
444
+ name: 'initialData',
445
+ type: 'json',
446
+ displayOptions: {
447
+ show: {
448
+ resource: ['worksheet'],
449
+ worksheetOperation: ['createWorksheet'],
450
+ },
451
+ },
452
+ default: '[]',
453
+ placeholder: '[{"Name": "John", "Age": 30}]',
454
+ description: 'Initial data as array of objects (optional)',
455
+ },
456
+ {
457
+ displayName: 'Include Hidden Sheets',
458
+ name: 'includeHidden',
459
+ type: 'boolean',
460
+ displayOptions: {
461
+ show: {
462
+ resource: ['worksheet'],
463
+ worksheetOperation: ['listWorksheets'],
464
+ },
465
+ },
466
+ default: false,
467
+ description: 'Whether to include hidden worksheets in the list',
468
+ },
469
+ // Auto Save Option
470
+ {
471
+ displayName: 'Auto Save',
472
+ name: 'autoSave',
473
+ type: 'boolean',
474
+ displayOptions: {
475
+ show: {
476
+ inputMode: ['filePath'],
477
+ },
478
+ },
479
+ default: true,
480
+ description: 'Whether to automatically save changes to file',
481
+ },
482
+ ],
483
+ };
484
+ this.methods = {
485
+ loadOptions: {
486
+ // Get available worksheets
487
+ async getWorksheets() {
488
+ try {
489
+ const inputMode = this.getNodeParameter('inputMode');
490
+ if (inputMode !== 'filePath') {
491
+ return [
492
+ {
493
+ name: 'Sheet name only available in file path mode',
494
+ value: '',
495
+ },
496
+ ];
497
+ }
498
+ const filePath = this.getNodeParameter('filePath');
499
+ if (!filePath || filePath.trim() === '') {
500
+ return [
501
+ {
502
+ name: 'Please specify file path first',
503
+ value: '',
504
+ },
505
+ ];
506
+ }
507
+ // Check file exists and is accessible
508
+ await fs.access(filePath);
509
+ const workbook = new ExcelJS.Workbook();
510
+ await workbook.xlsx.readFile(filePath);
511
+ const sheets = workbook.worksheets.map((sheet) => ({
512
+ name: sheet.name,
513
+ value: sheet.name,
514
+ }));
515
+ if (sheets.length === 0) {
516
+ return [
517
+ {
518
+ name: 'No worksheets found in file',
519
+ value: '',
520
+ },
521
+ ];
522
+ }
523
+ return sheets;
524
+ }
525
+ catch (error) {
526
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
527
+ return [
528
+ {
529
+ name: `Error: ${errorMessage}`,
530
+ value: '',
531
+ },
532
+ ];
533
+ }
534
+ },
535
+ // Get available columns
536
+ async getColumns() {
537
+ const inputMode = this.getNodeParameter('inputMode');
538
+ if (inputMode !== 'filePath') {
539
+ return [
540
+ {
541
+ name: 'Column detection only available in file path mode',
542
+ value: '',
543
+ },
544
+ ];
545
+ }
546
+ const filePath = this.getNodeParameter('filePath');
547
+ const sheetName = this.getNodeParameter('sheetNameOptions', '');
548
+ if (!filePath || !sheetName) {
549
+ return [
550
+ {
551
+ name: 'Please specify file path and sheet name first',
552
+ value: '',
553
+ },
554
+ ];
555
+ }
556
+ try {
557
+ const workbook = new ExcelJS.Workbook();
558
+ await workbook.xlsx.readFile(filePath);
559
+ const worksheet = workbook.getWorksheet(sheetName);
560
+ if (!worksheet) {
561
+ return [
562
+ {
563
+ name: 'Sheet not found',
564
+ value: '',
565
+ },
566
+ ];
567
+ }
568
+ const headerRow = worksheet.getRow(1);
569
+ const columns = [];
570
+ headerRow.eachCell((cell) => {
571
+ if (cell.value) {
572
+ const columnName = cell.value.toString().trim();
573
+ columns.push({
574
+ name: columnName,
575
+ value: columnName,
576
+ });
577
+ }
578
+ });
579
+ return columns.length > 0
580
+ ? columns
581
+ : [{ name: 'No columns found', value: '' }];
582
+ }
583
+ catch (error) {
584
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
585
+ return [
586
+ {
587
+ name: `Error: ${errorMessage}`,
588
+ value: '',
589
+ },
590
+ ];
591
+ }
592
+ },
593
+ },
594
+ };
595
+ }
596
+ async execute() {
597
+ const items = this.getInputData();
598
+ const returnData = [];
599
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
600
+ try {
601
+ const resource = this.getNodeParameter('resource', itemIndex);
602
+ const inputMode = this.getNodeParameter('inputMode', itemIndex);
603
+ // Load workbook
604
+ let workbook;
605
+ let filePath = '';
606
+ let result;
607
+ if (inputMode === 'filePath') {
608
+ filePath = this.getNodeParameter('filePath', itemIndex);
609
+ workbook = new ExcelJS.Workbook();
610
+ await workbook.xlsx.readFile(filePath);
611
+ }
612
+ else {
613
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex);
614
+ await this.helpers.assertBinaryData(itemIndex, binaryPropertyName);
615
+ const buffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName);
616
+ workbook = new ExcelJS.Workbook();
617
+ await workbook.xlsx.load(buffer);
618
+ }
619
+ if (resource === 'row') {
620
+ const operation = this.getNodeParameter('operation', itemIndex);
621
+ let sheetName = inputMode === 'filePath'
622
+ ? this.getNodeParameter('sheetNameOptions', itemIndex, '')
623
+ : this.getNodeParameter('sheetName', itemIndex, '');
624
+ // If no sheet name specified, use the first worksheet
625
+ if (!sheetName) {
626
+ const firstWorksheet = workbook.worksheets[0];
627
+ if (!firstWorksheet) {
628
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No worksheets found in the workbook');
629
+ }
630
+ sheetName = firstWorksheet.name;
631
+ }
632
+ const worksheet = workbook.getWorksheet(sheetName);
633
+ if (!worksheet) {
634
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Worksheet "${sheetName}" not found`);
635
+ }
636
+ const columnMap = ExcelAI.getColumnMapping(worksheet);
637
+ switch (operation) {
638
+ case 'readRows':
639
+ result = await ExcelAI.handleReadRows(this, worksheet, itemIndex);
640
+ break;
641
+ case 'appendRow':
642
+ result = await ExcelAI.handleAppendRow(this, worksheet, itemIndex, columnMap);
643
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
644
+ await workbook.xlsx.writeFile(filePath);
645
+ }
646
+ break;
647
+ case 'insertRow':
648
+ result = await ExcelAI.handleInsertRow(this, worksheet, itemIndex, columnMap);
649
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
650
+ await workbook.xlsx.writeFile(filePath);
651
+ }
652
+ break;
653
+ case 'updateRow':
654
+ result = await ExcelAI.handleUpdateRow(this, worksheet, itemIndex, columnMap);
655
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
656
+ await workbook.xlsx.writeFile(filePath);
657
+ }
658
+ break;
659
+ case 'deleteRow':
660
+ result = await ExcelAI.handleDeleteRow(this, worksheet, itemIndex);
661
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
662
+ await workbook.xlsx.writeFile(filePath);
663
+ }
664
+ break;
665
+ case 'findRows':
666
+ result = await ExcelAI.handleFindRows(this, worksheet, itemIndex, columnMap);
667
+ break;
668
+ default:
669
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
670
+ }
671
+ }
672
+ else if (resource === 'worksheet') {
673
+ const worksheetOperation = this.getNodeParameter('worksheetOperation', itemIndex);
674
+ switch (worksheetOperation) {
675
+ case 'listWorksheets':
676
+ result = await ExcelAI.handleListWorksheets(this, workbook, itemIndex);
677
+ break;
678
+ case 'createWorksheet':
679
+ result = await ExcelAI.handleCreateWorksheet(this, workbook, itemIndex);
680
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
681
+ await workbook.xlsx.writeFile(filePath);
682
+ }
683
+ break;
684
+ case 'deleteWorksheet':
685
+ result = await ExcelAI.handleDeleteWorksheet(this, workbook, itemIndex);
686
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
687
+ await workbook.xlsx.writeFile(filePath);
688
+ }
689
+ break;
690
+ case 'renameWorksheet':
691
+ result = await ExcelAI.handleRenameWorksheet(this, workbook, itemIndex);
692
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
693
+ await workbook.xlsx.writeFile(filePath);
694
+ }
695
+ break;
696
+ case 'copyWorksheet':
697
+ result = await ExcelAI.handleCopyWorksheet(this, workbook, itemIndex);
698
+ if (inputMode === 'filePath' && this.getNodeParameter('autoSave', itemIndex, true)) {
699
+ await workbook.xlsx.writeFile(filePath);
700
+ }
701
+ break;
702
+ case 'getWorksheetInfo':
703
+ result = await ExcelAI.handleGetWorksheetInfo(this, workbook, itemIndex);
704
+ break;
705
+ default:
706
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown worksheet operation: ${worksheetOperation}`);
707
+ }
708
+ }
709
+ // Handle result output
710
+ if (Array.isArray(result)) {
711
+ result.forEach((item) => returnData.push({ json: item }));
712
+ }
713
+ else {
714
+ returnData.push({ json: result });
715
+ }
716
+ // Return binary data if in binary mode
717
+ if (inputMode === 'binaryData') {
718
+ const buffer = await workbook.xlsx.writeBuffer();
719
+ const binaryData = await this.helpers.prepareBinaryData(Buffer.from(buffer), 'modified.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
720
+ returnData[returnData.length - 1].binary = { data: binaryData };
721
+ }
722
+ }
723
+ catch (error) {
724
+ if (this.continueOnFail()) {
725
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
726
+ returnData.push({
727
+ json: {
728
+ error: errorMessage,
729
+ resource: this.getNodeParameter('resource', itemIndex, ''),
730
+ operation: this.getNodeParameter('operation', itemIndex, ''),
731
+ },
732
+ });
733
+ continue;
734
+ }
735
+ throw error;
736
+ }
737
+ }
738
+ return [returnData];
739
+ }
740
+ // ========== Helper Methods ==========
741
+ static getColumnMapping(worksheet) {
742
+ const columnMap = new Map();
743
+ const headerRow = worksheet.getRow(1);
744
+ headerRow.eachCell((cell, colNumber) => {
745
+ if (cell.value) {
746
+ const columnName = cell.value.toString().trim();
747
+ columnMap.set(columnName, colNumber);
748
+ }
749
+ });
750
+ return columnMap;
751
+ }
752
+ static mapRowData(rowData, columnMap) {
753
+ const rowArray = new Array(columnMap.size);
754
+ columnMap.forEach((colNumber, columnName) => {
755
+ const value = rowData[columnName];
756
+ rowArray[colNumber - 1] = value !== undefined ? value : '';
757
+ });
758
+ return rowArray;
759
+ }
760
+ static parseJsonInput(context, input) {
761
+ if (typeof input === 'string') {
762
+ try {
763
+ return JSON.parse(input);
764
+ }
765
+ catch (error) {
766
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
767
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid JSON format: ${errorMessage}`);
768
+ }
769
+ }
770
+ return input;
771
+ }
772
+ // ========== Row Operation Handlers ==========
773
+ static async handleReadRows(context, worksheet, itemIndex) {
774
+ const startRow = context.getNodeParameter('startRow', itemIndex, 2);
775
+ const endRow = context.getNodeParameter('endRow', itemIndex, 0);
776
+ const rows = [];
777
+ const headerRow = worksheet.getRow(1);
778
+ const headers = [];
779
+ headerRow.eachCell((cell, colNumber) => {
780
+ var _a;
781
+ headers[colNumber - 1] = ((_a = cell.value) === null || _a === void 0 ? void 0 : _a.toString()) || '';
782
+ });
783
+ const actualEndRow = endRow === 0 ? worksheet.rowCount : Math.min(endRow, worksheet.rowCount);
784
+ for (let rowNum = startRow; rowNum <= actualEndRow; rowNum++) {
785
+ const row = worksheet.getRow(rowNum);
786
+ const rowData = { _rowNumber: rowNum };
787
+ row.eachCell((cell, colNumber) => {
788
+ const header = headers[colNumber - 1];
789
+ if (header) {
790
+ rowData[header] = cell.value;
791
+ }
792
+ });
793
+ // Skip empty rows
794
+ const hasData = Object.keys(rowData).some(key => key !== '_rowNumber' && rowData[key]);
795
+ if (hasData) {
796
+ rows.push(rowData);
797
+ }
798
+ }
799
+ return rows;
800
+ }
801
+ static async handleAppendRow(context, worksheet, itemIndex, columnMap) {
802
+ const rowDataInput = context.getNodeParameter('rowData', itemIndex);
803
+ const data = ExcelAI.parseJsonInput(context, rowDataInput);
804
+ const rowArray = ExcelAI.mapRowData(data, columnMap);
805
+ worksheet.addRow(rowArray);
806
+ const rowNumber = worksheet.rowCount;
807
+ return {
808
+ success: true,
809
+ operation: 'appendRow',
810
+ rowNumber,
811
+ data,
812
+ message: `Row added successfully at row ${rowNumber}`,
813
+ };
814
+ }
815
+ static async handleInsertRow(context, worksheet, itemIndex, columnMap) {
816
+ const rowNumber = context.getNodeParameter('rowNumber', itemIndex);
817
+ const rowDataInput = context.getNodeParameter('rowData', itemIndex);
818
+ const data = ExcelAI.parseJsonInput(context, rowDataInput);
819
+ if (rowNumber < 2) {
820
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Cannot insert before header row (row 1)');
821
+ }
822
+ const rowArray = ExcelAI.mapRowData(data, columnMap);
823
+ worksheet.insertRow(rowNumber, rowArray);
824
+ return {
825
+ success: true,
826
+ operation: 'insertRow',
827
+ rowNumber,
828
+ data,
829
+ message: `Row inserted successfully at row ${rowNumber}`,
830
+ };
831
+ }
832
+ static async handleUpdateRow(context, worksheet, itemIndex, columnMap) {
833
+ const rowNumber = context.getNodeParameter('rowNumber', itemIndex);
834
+ const updatedDataInput = context.getNodeParameter('updatedData', itemIndex);
835
+ const data = ExcelAI.parseJsonInput(context, updatedDataInput);
836
+ if (rowNumber < 2) {
837
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Cannot update header row (row 1)');
838
+ }
839
+ const row = worksheet.getRow(rowNumber);
840
+ Object.entries(data).forEach(([columnName, value]) => {
841
+ const colNumber = columnMap.get(columnName);
842
+ if (colNumber) {
843
+ row.getCell(colNumber).value = value;
844
+ }
845
+ });
846
+ row.commit();
847
+ return {
848
+ success: true,
849
+ operation: 'updateRow',
850
+ rowNumber,
851
+ updatedFields: Object.keys(data),
852
+ message: `Row ${rowNumber} updated successfully`,
853
+ };
854
+ }
855
+ static async handleDeleteRow(context, worksheet, itemIndex) {
856
+ const rowNumber = context.getNodeParameter('rowNumber', itemIndex);
857
+ if (rowNumber < 2) {
858
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Cannot delete header row (row 1)');
859
+ }
860
+ worksheet.spliceRows(rowNumber, 1);
861
+ return {
862
+ success: true,
863
+ operation: 'deleteRow',
864
+ rowNumber,
865
+ message: `Row ${rowNumber} deleted successfully`,
866
+ };
867
+ }
868
+ static async handleFindRows(context, worksheet, itemIndex, columnMap) {
869
+ const searchColumn = context.getNodeParameter('searchColumn', itemIndex);
870
+ const searchValue = context.getNodeParameter('searchValue', itemIndex);
871
+ const matchType = context.getNodeParameter('matchType', itemIndex, 'exact');
872
+ const returnRowNumbers = context.getNodeParameter('returnRowNumbers', itemIndex, false);
873
+ const colNumber = columnMap.get(searchColumn);
874
+ if (!colNumber) {
875
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Column "${searchColumn}" not found`);
876
+ }
877
+ const matchingRows = [];
878
+ const headerRow = worksheet.getRow(1);
879
+ const headers = [];
880
+ headerRow.eachCell((cell, colNum) => {
881
+ var _a;
882
+ headers[colNum - 1] = ((_a = cell.value) === null || _a === void 0 ? void 0 : _a.toString()) || '';
883
+ });
884
+ worksheet.eachRow((row, rowNumber) => {
885
+ var _a;
886
+ if (rowNumber === 1)
887
+ return;
888
+ const cellValue = ((_a = row.getCell(colNumber).value) === null || _a === void 0 ? void 0 : _a.toString()) || '';
889
+ let isMatch = false;
890
+ const searchLower = searchValue.toLowerCase();
891
+ const cellLower = cellValue.toLowerCase();
892
+ switch (matchType) {
893
+ case 'exact':
894
+ isMatch = cellLower === searchLower;
895
+ break;
896
+ case 'contains':
897
+ isMatch = cellLower.includes(searchLower);
898
+ break;
899
+ case 'startsWith':
900
+ isMatch = cellLower.startsWith(searchLower);
901
+ break;
902
+ case 'endsWith':
903
+ isMatch = cellLower.endsWith(searchLower);
904
+ break;
905
+ }
906
+ if (isMatch) {
907
+ if (returnRowNumbers) {
908
+ matchingRows.push(rowNumber);
909
+ }
910
+ else {
911
+ const rowData = { _rowNumber: rowNumber };
912
+ row.eachCell((cell, colNum) => {
913
+ const header = headers[colNum - 1];
914
+ if (header) {
915
+ rowData[header] = cell.value;
916
+ }
917
+ });
918
+ matchingRows.push(rowData);
919
+ }
920
+ }
921
+ });
922
+ if (returnRowNumbers) {
923
+ return [
924
+ {
925
+ operation: 'findRows',
926
+ searchColumn,
927
+ searchValue,
928
+ matchType,
929
+ rowNumbers: matchingRows,
930
+ count: matchingRows.length,
931
+ },
932
+ ];
933
+ }
934
+ // Return message if no rows found
935
+ if (matchingRows.length === 0) {
936
+ return [
937
+ {
938
+ operation: 'findRows',
939
+ searchColumn,
940
+ searchValue,
941
+ matchType,
942
+ count: 0,
943
+ message: 'No matching rows found',
944
+ },
945
+ ];
946
+ }
947
+ return matchingRows;
948
+ }
949
+ // ========== Worksheet Operation Handlers ==========
950
+ static async handleListWorksheets(context, workbook, itemIndex) {
951
+ const includeHidden = context.getNodeParameter('includeHidden', itemIndex, false);
952
+ const worksheets = [];
953
+ workbook.eachSheet((worksheet, sheetId) => {
954
+ if (!includeHidden && worksheet.state !== 'visible') {
955
+ return;
956
+ }
957
+ worksheets.push({
958
+ id: sheetId,
959
+ name: worksheet.name,
960
+ rowCount: worksheet.rowCount,
961
+ columnCount: worksheet.columnCount,
962
+ state: worksheet.state,
963
+ });
964
+ });
965
+ return {
966
+ operation: 'listWorksheets',
967
+ count: worksheets.length,
968
+ worksheets,
969
+ };
970
+ }
971
+ static async handleCreateWorksheet(context, workbook, itemIndex) {
972
+ const newSheetName = context.getNodeParameter('newSheetName', itemIndex);
973
+ const initialDataInput = context.getNodeParameter('initialData', itemIndex, '[]');
974
+ const initialData = ExcelAI.parseJsonInput(context, initialDataInput);
975
+ // Check if worksheet already exists
976
+ if (workbook.getWorksheet(newSheetName)) {
977
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${newSheetName}" already exists`);
978
+ }
979
+ const worksheet = workbook.addWorksheet(newSheetName);
980
+ // Add initial data if provided
981
+ if (Array.isArray(initialData) && initialData.length > 0) {
982
+ const headers = Object.keys(initialData[0]);
983
+ worksheet.addRow(headers);
984
+ initialData.forEach((row) => {
985
+ const rowArray = headers.map((header) => { var _a; return (_a = row[header]) !== null && _a !== void 0 ? _a : ''; });
986
+ worksheet.addRow(rowArray);
987
+ });
988
+ }
989
+ return {
990
+ success: true,
991
+ operation: 'createWorksheet',
992
+ sheetName: newSheetName,
993
+ rowCount: worksheet.rowCount,
994
+ message: `Worksheet "${newSheetName}" created successfully`,
995
+ };
996
+ }
997
+ static async handleDeleteWorksheet(context, workbook, itemIndex) {
998
+ const worksheetName = context.getNodeParameter('worksheetNameOptions', itemIndex);
999
+ const worksheet = workbook.getWorksheet(worksheetName);
1000
+ if (!worksheet) {
1001
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${worksheetName}" not found`);
1002
+ }
1003
+ workbook.removeWorksheet(worksheet.id);
1004
+ return {
1005
+ success: true,
1006
+ operation: 'deleteWorksheet',
1007
+ sheetName: worksheetName,
1008
+ message: `Worksheet "${worksheetName}" deleted successfully`,
1009
+ };
1010
+ }
1011
+ static async handleRenameWorksheet(context, workbook, itemIndex) {
1012
+ const worksheetName = context.getNodeParameter('worksheetNameOptions', itemIndex);
1013
+ const newSheetName = context.getNodeParameter('newSheetName', itemIndex);
1014
+ const worksheet = workbook.getWorksheet(worksheetName);
1015
+ if (!worksheet) {
1016
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${worksheetName}" not found`);
1017
+ }
1018
+ // Check if new name already exists
1019
+ if (workbook.getWorksheet(newSheetName)) {
1020
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${newSheetName}" already exists`);
1021
+ }
1022
+ worksheet.name = newSheetName;
1023
+ return {
1024
+ success: true,
1025
+ operation: 'renameWorksheet',
1026
+ oldName: worksheetName,
1027
+ newName: newSheetName,
1028
+ message: `Worksheet renamed from "${worksheetName}" to "${newSheetName}" successfully`,
1029
+ };
1030
+ }
1031
+ static async handleCopyWorksheet(context, workbook, itemIndex) {
1032
+ const worksheetName = context.getNodeParameter('worksheetNameOptions', itemIndex);
1033
+ const newSheetName = context.getNodeParameter('newSheetName', itemIndex);
1034
+ const sourceWorksheet = workbook.getWorksheet(worksheetName);
1035
+ if (!sourceWorksheet) {
1036
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${worksheetName}" not found`);
1037
+ }
1038
+ // Check if new name already exists
1039
+ if (workbook.getWorksheet(newSheetName)) {
1040
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${newSheetName}" already exists`);
1041
+ }
1042
+ // Create new worksheet
1043
+ const newWorksheet = workbook.addWorksheet(newSheetName);
1044
+ // Copy all rows from source to new worksheet
1045
+ sourceWorksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => {
1046
+ const newRow = newWorksheet.getRow(rowNumber);
1047
+ row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
1048
+ const newCell = newRow.getCell(colNumber);
1049
+ newCell.value = cell.value;
1050
+ newCell.style = cell.style;
1051
+ });
1052
+ newRow.height = row.height;
1053
+ newRow.commit();
1054
+ });
1055
+ // Copy column widths
1056
+ sourceWorksheet.columns.forEach((column, index) => {
1057
+ if (column && newWorksheet.getColumn(index + 1)) {
1058
+ newWorksheet.getColumn(index + 1).width = column.width;
1059
+ }
1060
+ });
1061
+ return {
1062
+ success: true,
1063
+ operation: 'copyWorksheet',
1064
+ sourceName: worksheetName,
1065
+ newName: newSheetName,
1066
+ rowCount: newWorksheet.rowCount,
1067
+ message: `Worksheet "${worksheetName}" copied to "${newSheetName}" successfully`,
1068
+ };
1069
+ }
1070
+ static async handleGetWorksheetInfo(context, workbook, itemIndex) {
1071
+ const worksheetName = context.getNodeParameter('worksheetNameOptions', itemIndex);
1072
+ const worksheet = workbook.getWorksheet(worksheetName);
1073
+ if (!worksheet) {
1074
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Worksheet "${worksheetName}" not found`);
1075
+ }
1076
+ // Get column information
1077
+ const columns = [];
1078
+ const headerRow = worksheet.getRow(1);
1079
+ headerRow.eachCell((cell, colNumber) => {
1080
+ var _a;
1081
+ const column = worksheet.getColumn(colNumber);
1082
+ columns.push({
1083
+ index: colNumber,
1084
+ letter: ExcelAI.columnNumberToLetter(colNumber),
1085
+ header: ((_a = cell.value) === null || _a === void 0 ? void 0 : _a.toString()) || '',
1086
+ width: column.width || 10,
1087
+ });
1088
+ });
1089
+ return {
1090
+ operation: 'getWorksheetInfo',
1091
+ sheetName: worksheetName,
1092
+ rowCount: worksheet.rowCount,
1093
+ columnCount: worksheet.columnCount,
1094
+ actualRowCount: worksheet.actualRowCount,
1095
+ actualColumnCount: worksheet.actualColumnCount,
1096
+ state: worksheet.state,
1097
+ columns,
1098
+ };
1099
+ }
1100
+ static columnNumberToLetter(column) {
1101
+ let letter = '';
1102
+ while (column > 0) {
1103
+ const remainder = (column - 1) % 26;
1104
+ letter = String.fromCharCode(65 + remainder) + letter;
1105
+ column = Math.floor((column - 1) / 26);
1106
+ }
1107
+ return letter;
1108
+ }
1109
+ }
1110
+ exports.ExcelAI = ExcelAI;
1111
+ //# sourceMappingURL=ExcelAI.node.js.map