2019-06-23 03:35:23 -07:00
import {
BINARY_ENCODING ,
IExecuteFunctions ,
} from 'n8n-core' ;
import {
2020-10-01 05:01:39 -07:00
IDataObject ,
2019-06-23 03:35:23 -07:00
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
import {
read as xlsxRead ,
2020-10-12 02:41:34 -07:00
Sheet2JSONOpts ,
2019-06-23 03:35:23 -07:00
utils as xlsxUtils ,
2020-10-01 05:01:39 -07:00
WorkBook ,
2019-06-23 03:35:23 -07:00
write as xlsxWrite ,
WritingOptions ,
} from 'xlsx' ;
/ * *
* Flattens an object with deep data
*
* @param { IDataObject } data The object to flatten
* @returns
* /
function flattenObject ( data : IDataObject ) {
const returnData : IDataObject = { } ;
for ( const key1 of Object . keys ( data ) ) {
2020-04-12 08:58:49 -07:00
if ( data [ key1 ] !== null && ( typeof data [ key1 ] ) === 'object' ) {
2019-06-23 03:35:23 -07:00
const flatObject = flattenObject ( data [ key1 ] as IDataObject ) ;
for ( const key2 in flatObject ) {
if ( flatObject [ key2 ] === undefined ) {
continue ;
}
returnData [ ` ${ key1 } . ${ key2 } ` ] = flatObject [ key2 ] ;
}
} else {
returnData [ key1 ] = data [ key1 ] ;
}
}
return returnData ;
}
export class SpreadsheetFile implements INodeType {
description : INodeTypeDescription = {
displayName : 'Spreadsheet File' ,
name : 'spreadsheetFile' ,
icon : 'fa:table' ,
group : [ 'transform' ] ,
version : 1 ,
description : 'Reads and writes data from a spreadsheet file.' ,
defaults : {
name : 'Spreadsheet File' ,
color : '#2244FF' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
properties : [
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'options' ,
options : [
{
name : 'Read from file' ,
value : 'fromFile' ,
description : 'Reads data from a spreadsheet file' ,
} ,
{
name : 'Write to file' ,
value : 'toFile' ,
description : 'Writes the workflow data to a spreadsheet file' ,
} ,
] ,
default : 'fromFile' ,
description : 'The operation to perform.' ,
} ,
// ----------------------------------
// fromFile
// ----------------------------------
{
displayName : 'Binary Property' ,
name : 'binaryPropertyName' ,
type : 'string' ,
default : 'data' ,
required : true ,
displayOptions : {
show : {
operation : [
'fromFile' ,
] ,
} ,
} ,
placeholder : '' ,
description : 'Name of the binary property from which to read<br />the binary data of the spreadsheet file.' ,
} ,
// ----------------------------------
// toFile
// ----------------------------------
{
displayName : 'File Format' ,
name : 'fileFormat' ,
type : 'options' ,
options : [
{
2019-12-27 08:33:42 -08:00
name : 'CSV' ,
2019-06-23 03:35:23 -07:00
value : 'csv' ,
description : 'Comma-separated values' ,
} ,
{
2019-12-27 08:33:42 -08:00
name : 'HTML' ,
value : 'html' ,
description : 'HTML Table' ,
} ,
{
name : 'ODS' ,
2019-06-23 03:35:23 -07:00
value : 'ods' ,
description : 'OpenDocument Spreadsheet' ,
} ,
{
2019-12-27 08:33:42 -08:00
name : 'RTF' ,
2019-06-23 03:35:23 -07:00
value : 'rtf' ,
description : 'Rich Text Format' ,
} ,
{
2019-12-27 08:33:42 -08:00
name : 'XLS' ,
2019-06-23 03:35:23 -07:00
value : 'xls' ,
description : 'Excel' ,
} ,
2020-04-14 06:38:15 -07:00
{
name : 'XLSX' ,
value : 'xlsx' ,
description : 'Excel' ,
} ,
2019-06-23 03:35:23 -07:00
] ,
default : 'xls' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'toFile' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
description : 'The format of the file to save the data as.' ,
} ,
{
displayName : 'Binary Property' ,
name : 'binaryPropertyName' ,
type : 'string' ,
default : 'data' ,
required : true ,
displayOptions : {
show : {
operation : [
'toFile' ,
] ,
} ,
} ,
placeholder : '' ,
description : 'Name of the binary property in which to save<br />the binary data of the spreadsheet file.' ,
} ,
2019-11-02 13:33:25 -07:00
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
options : [
2020-04-29 14:12:16 -07:00
{
displayName : 'Compression' ,
name : 'compression' ,
type : 'boolean' ,
displayOptions : {
show : {
'/operation' : [
'toFile' ,
] ,
'/fileFormat' : [
'xlsx' ,
'ods' ,
] ,
} ,
} ,
default : false ,
description : 'Weather compression will be applied or not' ,
} ,
2019-11-02 13:33:25 -07:00
{
displayName : 'File Name' ,
name : 'fileName' ,
type : 'string' ,
2019-12-27 06:50:22 -08:00
displayOptions : {
show : {
'/operation' : [
'toFile' ,
] ,
} ,
} ,
2019-11-02 13:33:25 -07:00
default : '' ,
description : 'File name to set in binary data. By default will "spreadsheet.<fileFormat>" be used.' ,
} ,
2019-12-27 06:50:22 -08:00
{
displayName : 'RAW Data' ,
name : 'rawData' ,
type : 'boolean' ,
displayOptions : {
show : {
'/operation' : [
2020-10-22 06:46:03 -07:00
'fromFile' ,
2019-12-27 06:50:22 -08:00
] ,
} ,
} ,
default : false ,
description : 'If the data should be returned RAW instead of parsed.' ,
} ,
2020-03-26 16:25:45 -07:00
{
displayName : 'Read As String' ,
name : 'readAsString' ,
type : 'boolean' ,
displayOptions : {
show : {
'/operation' : [
2020-10-22 06:46:03 -07:00
'fromFile' ,
2020-03-26 16:25:45 -07:00
] ,
} ,
} ,
default : false ,
description : 'In some cases and file formats, it is necessary to read<br />specifically as string else some special character get interpreted wrong.' ,
} ,
2020-10-12 02:41:34 -07:00
{
displayName : 'Range' ,
name : 'range' ,
type : 'string' ,
displayOptions : {
show : {
'/operation' : [
2020-10-22 06:46:03 -07:00
'fromFile' ,
2020-10-12 02:41:34 -07:00
] ,
} ,
} ,
default : '' ,
description : 'The range to read from the table.<br />If set to a number it will be the starting row.<br />If set to string it will be used as A1-style bounded range.' ,
} ,
2019-12-27 08:54:51 -08:00
{
displayName : 'Sheet Name' ,
name : 'sheetName' ,
type : 'string' ,
displayOptions : {
show : {
'/operation' : [
'fromFile' ,
] ,
} ,
} ,
default : 'Sheet' ,
description : 'Name of the sheet to read from in the spreadsheet (if supported). If not set, the first one gets chosen.' ,
} ,
2019-12-27 08:33:16 -08:00
{
displayName : 'Sheet Name' ,
name : 'sheetName' ,
type : 'string' ,
displayOptions : {
show : {
'/operation' : [
'toFile' ,
] ,
'/fileFormat' : [
'ods' ,
'xls' ,
2020-04-14 06:38:15 -07:00
'xlsx' ,
2019-12-27 08:33:16 -08:00
] ,
} ,
} ,
default : 'Sheet' ,
2019-12-27 08:54:51 -08:00
description : 'Name of the sheet to create in the spreadsheet.' ,
2019-12-27 08:33:16 -08:00
} ,
2019-11-02 13:33:25 -07:00
] ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-06-23 03:35:23 -07:00
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
const operation = this . getNodeParameter ( 'operation' , 0 ) as string ;
const newItems : INodeExecutionData [ ] = [ ] ;
if ( operation === 'fromFile' ) {
// Read data from spreadsheet file to workflow
let item : INodeExecutionData ;
for ( let i = 0 ; i < items . length ; i ++ ) {
item = items [ i ] ;
2019-12-27 06:50:22 -08:00
const binaryPropertyName = this . getNodeParameter ( 'binaryPropertyName' , i ) as string ;
const options = this . getNodeParameter ( 'options' , i , { } ) as IDataObject ;
2019-06-23 03:35:23 -07:00
if ( item . binary === undefined || item . binary [ binaryPropertyName ] === undefined ) {
// Property did not get found on item
continue ;
}
// Read the binary spreadsheet data
const binaryData = Buffer . from ( item . binary [ binaryPropertyName ] . data , BINARY_ENCODING ) ;
2020-03-26 16:25:45 -07:00
let workbook ;
if ( options . readAsString === true ) {
workbook = xlsxRead ( binaryData . toString ( ) , { type : 'string' , raw : options.rawData as boolean } ) ;
} else {
workbook = xlsxRead ( binaryData , { raw : options.rawData as boolean } ) ;
}
2019-06-23 03:35:23 -07:00
if ( workbook . SheetNames . length === 0 ) {
2019-12-27 08:54:51 -08:00
throw new Error ( 'Spreadsheet does not have any sheets!' ) ;
}
let sheetName = workbook . SheetNames [ 0 ] ;
if ( options . sheetName ) {
if ( ! workbook . SheetNames . includes ( options . sheetName as string ) ) {
throw new Error ( ` Spreadsheet does not contain sheet called " ${ options . sheetName } "! ` ) ;
}
sheetName = options . sheetName as string ;
2019-06-23 03:35:23 -07:00
}
// Convert it to json
2020-10-12 02:41:34 -07:00
const sheetToJsonOptions : Sheet2JSONOpts = { } ;
if ( options . range ) {
if ( isNaN ( options . range as number ) ) {
sheetToJsonOptions . range = options . range ;
} else {
sheetToJsonOptions . range = parseInt ( options . range as string , 10 ) ;
}
}
const sheetJson = xlsxUtils . sheet_to_json ( workbook . Sheets [ sheetName ] , sheetToJsonOptions ) ;
2019-06-23 03:35:23 -07:00
// Check if data could be found in file
if ( sheetJson . length === 0 ) {
continue ;
}
// Add all the found data columns to the workflow data
for ( const rowData of sheetJson ) {
newItems . push ( { json : rowData } as INodeExecutionData ) ;
}
}
return this . prepareOutputData ( newItems ) ;
} else if ( operation === 'toFile' ) {
// Write the workflow data to spreadsheet file
2019-12-27 07:32:04 -08:00
const binaryPropertyName = this . getNodeParameter ( 'binaryPropertyName' , 0 ) as string ;
const fileFormat = this . getNodeParameter ( 'fileFormat' , 0 ) as string ;
const options = this . getNodeParameter ( 'options' , 0 , { } ) as IDataObject ;
2019-06-23 03:35:23 -07:00
// Get the json data of the items and flatten it
let item : INodeExecutionData ;
const itemData : IDataObject [ ] = [ ] ;
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
item = items [ itemIndex ] ;
itemData . push ( flattenObject ( item . json ) ) ;
}
const ws = xlsxUtils . json_to_sheet ( itemData ) ;
const wopts : WritingOptions = {
bookSST : false ,
2020-10-22 06:46:03 -07:00
type : 'buffer' ,
2019-06-23 03:35:23 -07:00
} ;
if ( fileFormat === 'csv' ) {
wopts . bookType = 'csv' ;
} else if ( fileFormat === 'html' ) {
wopts . bookType = 'html' ;
} else if ( fileFormat === 'rtf' ) {
wopts . bookType = 'rtf' ;
} else if ( fileFormat === 'ods' ) {
wopts . bookType = 'ods' ;
2020-04-29 14:12:16 -07:00
if ( options . compression ) {
wopts . compression = true ;
}
2019-06-23 03:35:23 -07:00
} else if ( fileFormat === 'xls' ) {
2020-04-13 08:14:14 -07:00
wopts . bookType = 'xls' ;
2020-04-14 06:38:15 -07:00
} else if ( fileFormat === 'xlsx' ) {
wopts . bookType = 'xlsx' ;
2020-04-29 14:12:16 -07:00
if ( options . compression ) {
wopts . compression = true ;
}
2019-06-23 03:35:23 -07:00
}
// Convert the data in the correct format
2019-12-27 08:33:16 -08:00
const sheetName = options . sheetName as string || 'Sheet' ;
2019-06-23 03:35:23 -07:00
const wb : WorkBook = {
SheetNames : [ sheetName ] ,
Sheets : {
[ sheetName ] : ws ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
} ;
const wbout = xlsxWrite ( wb , wopts ) ;
// Create a new item with only the binary spreadsheet data
const newItem : INodeExecutionData = {
json : { } ,
binary : { } ,
} ;
2019-11-02 13:33:25 -07:00
let fileName = ` spreadsheet. ${ fileFormat } ` ;
if ( options . fileName !== undefined ) {
fileName = options . fileName as string ;
}
newItem . binary ! [ binaryPropertyName ] = await this . helpers . prepareBinaryData ( wbout , fileName ) ;
2019-06-23 03:35:23 -07:00
const newItems = [ ] ;
newItems . push ( newItem ) ;
return this . prepareOutputData ( newItems ) ;
} else {
throw new Error ( ` The operation " ${ operation } " is not supported! ` ) ;
}
}
}