n8n/packages/nodes-base/nodes/Files/ExtractFromFile/actions/moveTo.operation.ts
Michael Kret 5e16dd4ab4
feat(core): Improvements/overhaul for nodes working with binary data (#7651)
Github issue / Community forum post (link here to close automatically):

---------

Co-authored-by: Giulio Andreini <andreini@netseven.it>
Co-authored-by: Marcus <marcus@n8n.io>
2024-01-03 13:08:16 +02:00

193 lines
4.9 KiB
TypeScript

import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import { BINARY_ENCODING, NodeOperationError, deepCopy, jsonParse } from 'n8n-workflow';
import { encodeDecodeOptions } from '@utils/descriptions';
import { updateDisplayOptions } from '@utils/utilities';
import get from 'lodash/get';
import set from 'lodash/set';
import unset from 'lodash/unset';
import iconv from 'iconv-lite';
import { icsCalendarToObject } from 'ts-ics';
export const properties: INodeProperties[] = [
{
displayName: 'Input Binary Field',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
placeholder: 'e.g data',
hint: 'The name of the input field containing the file data to be processed',
},
{
displayName: 'Destination Output Field',
name: 'destinationKey',
type: 'string',
default: 'data',
required: true,
placeholder: 'e.g data',
description: 'The name of the output field that will contain the extracted data',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'File Encoding',
name: 'encoding',
type: 'options',
options: encodeDecodeOptions,
default: 'utf8',
description: 'Specify the encoding of the file, defaults to UTF-8',
},
{
displayName: 'Strip BOM',
name: 'stripBOM',
displayOptions: {
show: {
encoding: ['utf8', 'cesu8', 'ucs2'],
},
},
type: 'boolean',
default: true,
description:
'Whether to strip the BOM (Byte Order Mark) from the file, this could help in an environment where the presence of the BOM is causing issues or inconsistencies',
},
{
displayName: 'Keep Source',
name: 'keepSource',
type: 'options',
default: 'json',
options: [
{
name: 'JSON',
value: 'json',
description: 'Include JSON data of the input item',
},
{
name: 'Binary',
value: 'binary',
description: 'Include binary data of the input item',
},
{
name: 'Both',
value: 'both',
description: 'Include both JSON and binary data of the input item',
},
],
},
],
},
];
const displayOptions = {
show: {
operation: ['binaryToPropery', 'fromJson', 'text', 'fromIcs', 'xml'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
operation: string,
) {
const returnData: INodeExecutionData[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const item = items[itemIndex];
const options = this.getNodeParameter('options', itemIndex);
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex);
const newItem: INodeExecutionData = {
json: {},
pairedItem: { item: itemIndex },
};
const value = get(item.binary, binaryPropertyName);
if (!value) continue;
const encoding = (options.encoding as string) || 'utf8';
const buffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName);
if (options.keepSource && options.keepSource !== 'binary') {
newItem.json = deepCopy(item.json);
}
let convertedValue: string | IDataObject;
if (operation !== 'binaryToPropery') {
convertedValue = iconv.decode(buffer, encoding, {
stripBOM: options.stripBOM as boolean,
});
} else {
convertedValue = Buffer.from(buffer).toString(BINARY_ENCODING);
}
if (operation === 'fromJson') {
if (convertedValue === '') {
convertedValue = {};
} else {
convertedValue = jsonParse(convertedValue);
}
}
if (operation === 'fromIcs') {
convertedValue = icsCalendarToObject(convertedValue as string);
}
const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string;
set(newItem.json, destinationKey, convertedValue);
if (options.keepSource === 'binary' || options.keepSource === 'both') {
newItem.binary = item.binary;
} else {
// this binary data would not be included, but there also might be other binary data
// which should be included, copy it over and unset current binary data
newItem.binary = deepCopy(item.binary);
unset(newItem.binary, binaryPropertyName);
}
returnData.push(newItem);
} catch (error) {
let errorDescription;
if (error.message.includes('Unexpected token')) {
error.message = "The file selected in 'Input Binary Field' is not in JSON format";
errorDescription =
"Try to change the operation or select a JSON file in 'Input Binary Field'";
}
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
pairedItem: {
item: itemIndex,
},
});
continue;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
description: errorDescription,
});
}
}
return returnData;
}