first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.convertToFile",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.converttofile/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"CSV",
|
||||
"Spreadsheet",
|
||||
"Excel",
|
||||
"xls",
|
||||
"xlsx",
|
||||
"ods",
|
||||
"tabular",
|
||||
"encode",
|
||||
"encoding",
|
||||
"Move Binary Data",
|
||||
"Binary",
|
||||
"File",
|
||||
"JSON",
|
||||
"HTML",
|
||||
"ICS",
|
||||
"iCal",
|
||||
"RTF",
|
||||
"64",
|
||||
"Base64"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Files", "Data Transformation"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import * as iCall from './actions/iCall.operation';
|
||||
import * as spreadsheet from './actions/spreadsheet.operation';
|
||||
import * as toBinary from './actions/toBinary.operation';
|
||||
import * as toJson from './actions/toJson.operation';
|
||||
import * as toText from './actions/toText.operation';
|
||||
|
||||
export class ConvertToFile implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Convert to File',
|
||||
name: 'convertToFile',
|
||||
icon: { light: 'file:convertToFile.svg', dark: 'file:convertToFile.dark.svg' },
|
||||
group: ['input'],
|
||||
version: [1, 1.1],
|
||||
description: 'Convert JSON data to binary data',
|
||||
defaults: {
|
||||
name: 'Convert to File',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Convert to CSV',
|
||||
value: 'csv',
|
||||
action: 'Convert to CSV',
|
||||
description: 'Transform input data into a CSV file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to HTML',
|
||||
value: 'html',
|
||||
action: 'Convert to HTML',
|
||||
description: 'Transform input data into a table in an HTML file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to ICS',
|
||||
value: 'iCal',
|
||||
action: 'Convert to ICS',
|
||||
description: 'Converts each input item to an ICS event file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to JSON',
|
||||
value: 'toJson',
|
||||
action: 'Convert to JSON',
|
||||
description: 'Transform input data into a single or multiple JSON files',
|
||||
},
|
||||
{
|
||||
name: 'Convert to ODS',
|
||||
value: 'ods',
|
||||
action: 'Convert to ODS',
|
||||
description: 'Transform input data into an ODS file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to RTF',
|
||||
value: 'rtf',
|
||||
action: 'Convert to RTF',
|
||||
description: 'Transform input data into a table in an RTF file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to Text File',
|
||||
value: 'toText',
|
||||
action: 'Convert to text file',
|
||||
description: 'Transform input data string into a file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to XLS',
|
||||
value: 'xls',
|
||||
action: 'Convert to XLS',
|
||||
description: 'Transform input data into an Excel file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to XLSX',
|
||||
value: 'xlsx',
|
||||
action: 'Convert to XLSX',
|
||||
description: 'Transform input data into an Excel file',
|
||||
},
|
||||
{
|
||||
name: 'Move Base64 String to File',
|
||||
value: 'toBinary',
|
||||
action: 'Move base64 string to file',
|
||||
description: 'Convert a base64-encoded string into its original file format',
|
||||
},
|
||||
],
|
||||
default: 'csv',
|
||||
},
|
||||
...spreadsheet.description,
|
||||
...toBinary.description,
|
||||
...toText.description,
|
||||
...toJson.description,
|
||||
...iCall.description,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
const items = this.getInputData();
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
if (spreadsheet.operations.includes(operation)) {
|
||||
returnData = await spreadsheet.execute.call(this, items, operation);
|
||||
}
|
||||
|
||||
if (operation === 'toJson') {
|
||||
returnData = await toJson.execute.call(this, items);
|
||||
}
|
||||
|
||||
if (operation === 'toBinary') {
|
||||
returnData = await toBinary.execute.call(this, items);
|
||||
}
|
||||
|
||||
if (operation === 'toText') {
|
||||
returnData = await toText.execute.call(this, items);
|
||||
}
|
||||
|
||||
if (operation === 'iCal') {
|
||||
returnData = await iCall.execute.call(this, items);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import * as createEvent from '../../../ICalendar/createEvent.operation';
|
||||
|
||||
export const description: INodeProperties[] = updateDisplayOptions(
|
||||
{
|
||||
show: {
|
||||
operation: ['iCal'],
|
||||
},
|
||||
},
|
||||
createEvent.description,
|
||||
);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const returnData = await createEvent.execute.call(this, items);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
NodeOperationError,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { JsonToSpreadsheetBinaryOptions, JsonToSpreadsheetBinaryFormat } from '@utils/binary';
|
||||
import { convertJsonToSpreadsheetBinary } from '@utils/binary';
|
||||
import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
export const operations = ['csv', 'html', 'rtf', 'ods', 'xls', 'xlsx'];
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Compression',
|
||||
name: 'compression',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['xlsx', 'ods'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to reduce the output file size',
|
||||
},
|
||||
{
|
||||
displayName: 'Delimiter',
|
||||
name: 'delimiter',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['csv'],
|
||||
},
|
||||
},
|
||||
default: ',',
|
||||
description: 'The character to use to separate fields',
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
{
|
||||
displayName: 'Header Row',
|
||||
name: 'headerRow',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether the first row of the file contains the header names',
|
||||
},
|
||||
{
|
||||
displayName: 'Sheet Name',
|
||||
name: 'sheetName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['ods', 'xls', 'xlsx'],
|
||||
},
|
||||
},
|
||||
default: 'Sheet',
|
||||
description: 'Name of the sheet to create in the spreadsheet',
|
||||
placeholder: 'e.g. mySheet',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: operations,
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
operation: string,
|
||||
) {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const pairedItem = generatePairedItemData(items.length);
|
||||
try {
|
||||
const options = this.getNodeParameter('options', 0, {}) as JsonToSpreadsheetBinaryOptions;
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0, 'data');
|
||||
|
||||
const binaryData = await convertJsonToSpreadsheetBinary.call(
|
||||
this,
|
||||
items,
|
||||
operation as JsonToSpreadsheetBinaryFormat,
|
||||
options,
|
||||
'File',
|
||||
);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem,
|
||||
};
|
||||
|
||||
returnData = [newItem];
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem,
|
||||
});
|
||||
} else {
|
||||
throw new NodeOperationError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import type { JsonToBinaryOptions } from '@utils/binary';
|
||||
import { createBinaryFromJson } from '@utils/binary';
|
||||
import { encodeDecodeOptions } from '@utils/descriptions';
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Base64 Input Field',
|
||||
name: 'sourceProperty',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
requiresDataPath: 'single',
|
||||
description:
|
||||
"The name of the input field that contains the base64 string to convert to a file. Use dot-notation for deep fields (e.g. 'level1.level2.currentKey').",
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Add Byte Order Mark (BOM)',
|
||||
description:
|
||||
'Whether to add special marker at the start of your text file. This marker helps some programs understand how to read the file correctly.',
|
||||
name: 'addBOM',
|
||||
displayOptions: {
|
||||
show: {
|
||||
encoding: ['utf8', 'cesu8', 'ucs2'],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Data Is Base64',
|
||||
name: 'dataIsBase64',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether the data is already base64 encoded',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [1],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding',
|
||||
name: 'encoding',
|
||||
type: 'options',
|
||||
options: encodeDecodeOptions,
|
||||
default: 'utf8',
|
||||
description: 'Choose the character set to use to encode the data',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
dataIsBase64: [true],
|
||||
'@version': [{ _cnd: { gt: 1 } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. myFile',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
{
|
||||
displayName: 'MIME Type',
|
||||
name: 'mimeType',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g text/plain',
|
||||
description:
|
||||
'The MIME type of the output file. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types" target="_blank">Common MIME types</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['toBinary'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i, 'data');
|
||||
const sourceProperty = this.getNodeParameter('sourceProperty', i) as string;
|
||||
|
||||
let dataIsBase64 = true;
|
||||
if (nodeVersion === 1) {
|
||||
dataIsBase64 = options.dataIsBase64 !== false;
|
||||
}
|
||||
|
||||
const jsonToBinaryOptions: JsonToBinaryOptions = {
|
||||
sourceKey: sourceProperty,
|
||||
fileName: options.fileName as string,
|
||||
mimeType: options.mimeType as string,
|
||||
dataIsBase64,
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
itemIndex: i,
|
||||
};
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(this, items[i].json, jsonToBinaryOptions);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem: { item: i },
|
||||
};
|
||||
|
||||
returnData.push(newItem);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: i,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, { itemIndex: i });
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { createBinaryFromJson } from '@utils/binary';
|
||||
import { encodeDecodeOptions } from '@utils/descriptions';
|
||||
import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Mode',
|
||||
name: 'mode',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'All Items to One File',
|
||||
value: 'once',
|
||||
},
|
||||
{
|
||||
name: 'Each Item to Separate File',
|
||||
value: 'each',
|
||||
},
|
||||
],
|
||||
default: 'once',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Add Byte Order Mark (BOM)',
|
||||
name: 'addBOM',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to add special marker at the start of your text file. This marker helps some programs understand how to read the file correctly.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
encoding: ['utf8', 'cesu8', 'ucs2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Format',
|
||||
name: 'format',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to format the JSON data for easier reading',
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding',
|
||||
name: 'encoding',
|
||||
type: 'options',
|
||||
options: encodeDecodeOptions,
|
||||
default: 'utf8',
|
||||
description: 'Choose the character set to use to encode the data',
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. myFile.json',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['toJson'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const mode = this.getNodeParameter('mode', 0, 'once') as string;
|
||||
if (mode === 'once') {
|
||||
const pairedItem = generatePairedItemData(items.length);
|
||||
try {
|
||||
const options = this.getNodeParameter('options', 0, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0, 'data');
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(
|
||||
this,
|
||||
items.map((item) => item.json),
|
||||
{
|
||||
fileName: options.fileName as string,
|
||||
mimeType: 'application/json',
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
format: options.format as boolean,
|
||||
},
|
||||
);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem,
|
||||
};
|
||||
|
||||
returnData = [newItem];
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem,
|
||||
});
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i, 'data');
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(this, items[i].json, {
|
||||
fileName: options.fileName as string,
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
format: options.format as boolean,
|
||||
mimeType: 'application/json',
|
||||
itemIndex: i,
|
||||
});
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem: { item: i },
|
||||
};
|
||||
|
||||
returnData.push(newItem);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: i,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, { itemIndex: i });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import type { JsonToBinaryOptions } from '@utils/binary';
|
||||
import { createBinaryFromJson } from '@utils/binary';
|
||||
import { encodeDecodeOptions } from '@utils/descriptions';
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Text Input Field',
|
||||
name: 'sourceProperty',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
requiresDataPath: 'single',
|
||||
description:
|
||||
"The name of the input field that contains a string to convert to a file. Use dot-notation for deep fields (e.g. 'level1.level2.currentKey').",
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Add Byte Order Mark (BOM)',
|
||||
description:
|
||||
'Whether to add special marker at the start of your text file. This marker helps some programs understand how to read the file correctly.',
|
||||
name: 'addBOM',
|
||||
displayOptions: {
|
||||
show: {
|
||||
encoding: ['utf8', 'cesu8', 'ucs2'],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding',
|
||||
name: 'encoding',
|
||||
type: 'options',
|
||||
options: encodeDecodeOptions,
|
||||
default: 'utf8',
|
||||
description: 'Choose the character set to use to encode the data',
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. myFile',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['toText'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i, 'data');
|
||||
const sourceProperty = this.getNodeParameter('sourceProperty', i) as string;
|
||||
|
||||
const jsonToBinaryOptions: JsonToBinaryOptions = {
|
||||
sourceKey: sourceProperty,
|
||||
fileName: (options.fileName as string) || 'file.txt',
|
||||
mimeType: 'text/plain',
|
||||
dataIsBase64: false,
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
itemIndex: i,
|
||||
};
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(this, items[i].json, jsonToBinaryOptions);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem: { item: i },
|
||||
};
|
||||
|
||||
returnData.push(newItem);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: i,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, { itemIndex: i });
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.7619 2C13.2437 2 12.8236 2.42009 12.8236 2.93831V15.2526C13.3199 15.4643 13.7851 15.772 14.191 16.1757L21.2283 23.1739C22.0548 23.9958 22.5195 25.1132 22.5195 26.2788C22.5195 27.4443 22.0548 28.5618 21.2283 29.3837L14.191 36.3819C13.785 36.7856 13.3199 37.0932 12.8236 37.305V37.3527C12.8236 37.8709 13.2437 38.291 13.7619 38.291H39.0617C39.5799 38.291 40 37.8709 40 37.3527L40 15.794H27.1443C26.6261 15.794 26.206 15.3739 26.206 14.8557V2H13.7619Z" fill="#766DFB"/>
|
||||
<path d="M28.8645 2C28.7781 2 28.7081 2.07002 28.7081 2.15639V12.8227C28.7081 13.0818 28.9182 13.2919 29.1773 13.2919H39.8436C39.93 13.2919 40 13.2219 40 13.1355L40 12.6263C40 12.3778 39.9014 12.1395 39.726 11.9636L30.061 2.27561C29.885 2.09916 29.646 2 29.3967 2H28.8645Z" fill="#766DFB"/>
|
||||
<path d="M9.77268 34.6003C9.04186 33.8654 9.04517 32.6772 9.78007 31.9464L13.6315 28.1163L0.938311 28.1163C0.420096 28.1163 -2.26519e-08 27.6962 0 27.178L8.20297e-08 25.3014C1.04682e-07 24.7832 0.420095 24.3631 0.938311 24.3631H13.5529L9.78007 20.6112C9.04517 19.8804 9.04186 18.6922 9.77268 17.9573C10.5035 17.2224 11.6917 17.2191 12.4266 17.9499L19.4639 24.9481C19.8181 25.3004 20.0173 25.7793 20.0173 26.2788C20.0173 26.7783 19.8181 27.2572 19.4639 27.6095L12.4266 34.6077C11.6917 35.3385 10.5035 35.3352 9.77268 34.6003Z" fill="#766DFB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.7619 2C13.2437 2 12.8236 2.42009 12.8236 2.93831V15.2526C13.3199 15.4643 13.7851 15.772 14.191 16.1757L21.2283 23.1739C22.0548 23.9958 22.5195 25.1132 22.5195 26.2788C22.5195 27.4443 22.0548 28.5618 21.2283 29.3837L14.191 36.3819C13.785 36.7856 13.3199 37.0932 12.8236 37.305V37.3527C12.8236 37.8709 13.2437 38.291 13.7619 38.291H39.0617C39.5799 38.291 40 37.8709 40 37.3527L40 15.794H27.1443C26.6261 15.794 26.206 15.3739 26.206 14.8557V2H13.7619Z" fill="#3A42E9"/>
|
||||
<path d="M28.8645 2C28.7781 2 28.7081 2.07002 28.7081 2.15639V12.8227C28.7081 13.0818 28.9182 13.2919 29.1773 13.2919H39.8436C39.93 13.2919 40 13.2219 40 13.1355L40 12.6263C40 12.3778 39.9014 12.1395 39.726 11.9636L30.061 2.27561C29.885 2.09916 29.646 2 29.3967 2H28.8645Z" fill="#3A42E9"/>
|
||||
<path d="M9.77268 34.6003C9.04186 33.8654 9.04517 32.6772 9.78007 31.9464L13.6315 28.1163L0.938311 28.1163C0.420096 28.1163 -2.26519e-08 27.6962 0 27.178L8.20297e-08 25.3014C1.04682e-07 24.7832 0.420095 24.3631 0.938311 24.3631H13.5529L9.78007 20.6112C9.04517 19.8804 9.04186 18.6922 9.77268 17.9573C10.5035 17.2224 11.6917 17.2191 12.4266 17.9499L19.4639 24.9481C19.8181 25.3004 20.0173 25.7793 20.0173 26.2788C20.0173 26.7783 19.8181 27.2572 19.4639 27.6095L12.4266 34.6077C11.6917 35.3385 10.5035 35.3352 9.77268 34.6003Z" fill="#3A42E9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,5 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
describe('Test ConvertToFile Node', () => {
|
||||
new NodeTestHarness().setupTests();
|
||||
});
|
||||
@@ -0,0 +1,463 @@
|
||||
{
|
||||
"name": "Test ConvertToFile",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f2557b99-e65c-4136-9bc5-7cb328a62d30",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [300, 1040]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toBinary",
|
||||
"sourceProperty": "base64",
|
||||
"options": {}
|
||||
},
|
||||
"id": "71711ee7-9df5-456a-b1d0-def85b8d6669",
|
||||
"name": "Convert to File2",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [880, 540]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": {
|
||||
"id": "23423532",
|
||||
"name": "Jay Gatsby",
|
||||
"email": "gatsby@west-egg.com",
|
||||
"notes": "Keeps asking about a green light??",
|
||||
"country": "US",
|
||||
"created": "1925-04-10",
|
||||
"base64": "VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ="
|
||||
}
|
||||
},
|
||||
"id": "ff53ab4c-43dd-4c35-b066-59d59e4a8209",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [520, 1040]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"options": {}
|
||||
},
|
||||
"id": "6322369c-fef5-4172-bbd3-8738cbb67e05",
|
||||
"name": "Extract From File",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [1100, 540]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "b5aed50b-65d3-4356-b02d-cbeab7fb3d6e",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1320, 540]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toText",
|
||||
"sourceProperty": "notes",
|
||||
"options": {}
|
||||
},
|
||||
"id": "65c4c4ac-da33-4ba1-b337-51ee319a8652",
|
||||
"name": "Convert to Text File",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [880, 740]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"options": {}
|
||||
},
|
||||
"id": "b2f49de3-5d0d-4eff-8156-89e5a2a0edae",
|
||||
"name": "Extract From Text File",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [1100, 740]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "86d5e979-5ff0-4312-8b4f-7ff83f1eebd6",
|
||||
"name": "Text File Result",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1320, 740]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toJson",
|
||||
"options": {
|
||||
"format": true
|
||||
}
|
||||
},
|
||||
"id": "a938ea30-3acb-4618-a80a-6e759ba7d8db",
|
||||
"name": "Convert to JSON (with Formatting)",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [880, 920]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"options": {}
|
||||
},
|
||||
"id": "fa35a5ed-4746-48c5-b260-314c517cdd45",
|
||||
"name": "Extract From JSON (1)",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [1100, 920]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"options": {}
|
||||
},
|
||||
"id": "4a3c5c68-6621-4463-8623-83a6572ae760",
|
||||
"name": "Extract From JSON (2)",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [1100, 1120]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "dbd03dcb-435c-4af7-ada8-2492c69f1cd6",
|
||||
"name": "JSON Result (with Formatting)",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1320, 920]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toJson",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c461944d-3141-4a1d-abe1-a74e1f71615f",
|
||||
"name": "Convert to JSON",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [880, 1120]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "c9ca03b3-0f01-49f5-ab9d-ed4497829a09",
|
||||
"name": "Convert to CSV",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [880, 1320]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "1ad12799-7916-4780-aca9-9a966f9e5820",
|
||||
"name": "Extract From CSV",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [1100, 1320]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"delimiter": "|"
|
||||
}
|
||||
},
|
||||
"id": "58caeb17-7434-4808-b8c4-4ca443baecff",
|
||||
"name": "Convert to CSV (custom delimiter)",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [880, 1520]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "b113b6d9-7928-4de1-bf7f-e2984d124edf",
|
||||
"name": "CSV Result",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1320, 1320]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "12554f8d-592a-4500-80f7-42518840f718",
|
||||
"name": "JSON Result",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1320, 1120]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "8a6ab018-9f38-4a5b-9483-769bf38f0b7c",
|
||||
"name": "CSV Result (custom delimiter)",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1320, 1520]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"delimiter": "|"
|
||||
}
|
||||
},
|
||||
"id": "e8a4114f-5264-4255-982e-4690c950fb86",
|
||||
"name": "Extract From Custom Delimiter",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [1100, 1520]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"data": "This is some text encoded as base64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Text File Result": [
|
||||
{
|
||||
"json": {
|
||||
"data": "Keeps asking about a green light??"
|
||||
}
|
||||
}
|
||||
],
|
||||
"JSON Result (with Formatting)": [
|
||||
{
|
||||
"json": {
|
||||
"data": "[\n {\n \"id\": \"23423532\",\n \"name\": \"Jay Gatsby\",\n \"email\": \"gatsby@west-egg.com\",\n \"notes\": \"Keeps asking about a green light??\",\n \"country\": \"US\",\n \"created\": \"1925-04-10\",\n \"base64\": \"VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ=\"\n }\n]"
|
||||
}
|
||||
}
|
||||
],
|
||||
"CSV Result": [
|
||||
{
|
||||
"json": {
|
||||
"id": "23423532",
|
||||
"name": "Jay Gatsby",
|
||||
"email": "gatsby@west-egg.com",
|
||||
"notes": "Keeps asking about a green light??",
|
||||
"country": "US",
|
||||
"created": "1925-04-10",
|
||||
"base64": "VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ="
|
||||
}
|
||||
}
|
||||
],
|
||||
"JSON Result": [
|
||||
{
|
||||
"json": {
|
||||
"data": "[{\"id\":\"23423532\",\"name\":\"Jay Gatsby\",\"email\":\"gatsby@west-egg.com\",\"notes\":\"Keeps asking about a green light??\",\"country\":\"US\",\"created\":\"1925-04-10\",\"base64\":\"VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ=\"}]"
|
||||
}
|
||||
}
|
||||
],
|
||||
"CSV Result (custom delimiter)": [
|
||||
{
|
||||
"json": {
|
||||
"id": "23423532",
|
||||
"name": "Jay Gatsby",
|
||||
"email": "gatsby@west-egg.com",
|
||||
"notes": "Keeps asking about a green light??",
|
||||
"country": "US",
|
||||
"created": "1925-04-10",
|
||||
"base64": "VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ="
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Convert to File2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to Text File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to JSON (with Formatting)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to JSON",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to CSV",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to CSV (custom delimiter)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to Text File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From Text File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From Text File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Text File Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to JSON (with Formatting)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From JSON (1)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From JSON (1)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "JSON Result (with Formatting)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From JSON (2)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "JSON Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to JSON": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From JSON (2)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to CSV": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From CSV",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From CSV": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "CSV Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to CSV (custom delimiter)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From Custom Delimiter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From Custom Delimiter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "CSV Result (custom delimiter)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "6555e00a-2c20-46f2-9a2c-fb368d557035",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
|
||||
},
|
||||
"id": "RSOCg1c3be66ZqVH",
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.extractFromFile",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.extractfromfile/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"CSV",
|
||||
"Spreadsheet",
|
||||
"Excel",
|
||||
"xls",
|
||||
"xlsx",
|
||||
"ods",
|
||||
"tabular",
|
||||
"decode",
|
||||
"decoding",
|
||||
"Move Binary Data",
|
||||
"Binary",
|
||||
"File",
|
||||
"PDF",
|
||||
"JSON",
|
||||
"HTML",
|
||||
"ICS",
|
||||
"iCal",
|
||||
"txt",
|
||||
"Text",
|
||||
"RTF",
|
||||
"XML",
|
||||
"64",
|
||||
"Base64",
|
||||
"Convert"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Files", "Data Transformation"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import * as moveTo from './actions/moveTo.operation';
|
||||
import * as pdf from './actions/pdf.operation';
|
||||
import * as spreadsheet from './actions/spreadsheet.operation';
|
||||
|
||||
export class ExtractFromFile implements INodeType {
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-missing-subtitle
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Extract from File',
|
||||
name: 'extractFromFile',
|
||||
icon: { light: 'file:extractFromFile.svg', dark: 'file:extractFromFile.dark.svg' },
|
||||
group: ['input'],
|
||||
version: [1, 1.1],
|
||||
description: 'Convert binary data to JSON',
|
||||
defaults: {
|
||||
name: 'Extract from File',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Extract From CSV',
|
||||
value: 'csv',
|
||||
action: 'Extract from CSV',
|
||||
description: 'Transform a CSV file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From HTML',
|
||||
value: 'html',
|
||||
action: 'Extract from HTML',
|
||||
description: 'Transform a table in an HTML file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From ICS',
|
||||
value: 'fromIcs',
|
||||
action: 'Extract from ICS',
|
||||
description: 'Transform a ICS file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From JSON',
|
||||
value: 'fromJson',
|
||||
action: 'Extract from JSON',
|
||||
description: 'Transform a JSON file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From ODS',
|
||||
value: 'ods',
|
||||
action: 'Extract from ODS',
|
||||
description: 'Transform an ODS file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From PDF',
|
||||
value: 'pdf',
|
||||
action: 'Extract from PDF',
|
||||
description: 'Extracts the content and metadata from a PDF file',
|
||||
},
|
||||
{
|
||||
name: 'Extract From RTF',
|
||||
value: 'rtf',
|
||||
action: 'Extract from RTF',
|
||||
description: 'Transform a table in an RTF file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From Text File',
|
||||
value: 'text',
|
||||
action: 'Extract from text file',
|
||||
description: 'Extracts the content of a text file',
|
||||
},
|
||||
{
|
||||
name: 'Extract From XML',
|
||||
value: 'xml',
|
||||
action: 'Extract from XML',
|
||||
description: 'Extracts the content of an XML file',
|
||||
},
|
||||
{
|
||||
name: 'Extract From XLS',
|
||||
value: 'xls',
|
||||
action: 'Extract from XLS',
|
||||
description: 'Transform an Excel file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Extract From XLSX',
|
||||
value: 'xlsx',
|
||||
action: 'Extract from XLSX',
|
||||
description: 'Transform an Excel file into output items',
|
||||
},
|
||||
{
|
||||
name: 'Move File to Base64 String',
|
||||
value: 'binaryToPropery',
|
||||
action: 'Move file to base64 string',
|
||||
description: 'Convert a file into a base64-encoded string',
|
||||
},
|
||||
],
|
||||
default: 'csv',
|
||||
},
|
||||
...spreadsheet.description,
|
||||
...moveTo.description,
|
||||
...pdf.description,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
const version = this.getNode().typeVersion;
|
||||
const items = this.getInputData();
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
if (spreadsheet.operations.includes(operation)) {
|
||||
returnData = await spreadsheet.execute.call(this, items, 'operation', {
|
||||
failOnCsvBufferError: version > 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (['binaryToPropery', 'fromJson', 'text', 'fromIcs', 'xml'].includes(operation)) {
|
||||
returnData = await moveTo.execute.call(this, items, operation);
|
||||
}
|
||||
|
||||
if (operation === 'pdf') {
|
||||
returnData = await pdf.execute.call(this, items);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import iconv from 'iconv-lite';
|
||||
import set from 'lodash/set';
|
||||
import unset from 'lodash/unset';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
IBinaryData,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
NodeOperationError,
|
||||
deepCopy,
|
||||
jsonParse,
|
||||
BINARY_MODE_COMBINED,
|
||||
} from 'n8n-workflow';
|
||||
import { icsCalendarToObject } from 'ts-ics';
|
||||
|
||||
import { encodeDecodeOptions } from '@utils/descriptions';
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
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[] = [];
|
||||
const { binaryMode } = this.getWorkflowSettings();
|
||||
|
||||
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) as
|
||||
| string
|
||||
| IBinaryData;
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
pairedItem: { item: itemIndex },
|
||||
};
|
||||
|
||||
const value = this.helpers.assertBinaryData(itemIndex, binaryPropertyName);
|
||||
|
||||
if (!value) continue;
|
||||
|
||||
const buffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName);
|
||||
const encoding = (options.encoding as string) || this.helpers.detectBinaryEncoding(buffer);
|
||||
|
||||
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 (typeof binaryPropertyName === 'string' && binaryMode !== BINARY_MODE_COMBINED) {
|
||||
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 (typeof error.message === 'string' && 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;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import unset from 'lodash/unset';
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError, deepCopy } from 'n8n-workflow';
|
||||
|
||||
import { extractDataFromPDF } from '@utils/binary';
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
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 binary field containing the file to be extracted',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Join Pages',
|
||||
name: 'joinPages',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to join the text from all pages or return an array of text from each page',
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Max Pages',
|
||||
name: 'maxPages',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Maximum number of pages to include',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
typeOptions: { password: true },
|
||||
default: '',
|
||||
description: 'Prowide password, if the PDF is encrypted',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['pdf'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
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 json = await extractDataFromPDF.call(
|
||||
this,
|
||||
binaryPropertyName,
|
||||
options.password as string,
|
||||
options.maxPages as number,
|
||||
options.joinPages as boolean,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
pairedItem: { item: itemIndex },
|
||||
};
|
||||
|
||||
if (options.keepSource && options.keepSource !== 'binary') {
|
||||
newItem.json = { ...deepCopy(item.json), ...json };
|
||||
} else {
|
||||
newItem.json = json;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: itemIndex,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, { itemIndex });
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as fromFile from '../../../SpreadsheetFile/v2/fromFile.operation';
|
||||
|
||||
export const operations = ['csv', 'html', 'rtf', 'ods', 'xls', 'xlsx'];
|
||||
|
||||
export const description: INodeProperties[] = fromFile.description
|
||||
.filter((property) => property.name !== 'fileFormat')
|
||||
.map((property) => {
|
||||
const newProperty = { ...property };
|
||||
newProperty.displayOptions = {
|
||||
show: {
|
||||
operation: operations,
|
||||
},
|
||||
};
|
||||
|
||||
if (newProperty.name === 'options') {
|
||||
newProperty.options = (newProperty.options as INodeProperties[]).map((option) => {
|
||||
let newOption = option;
|
||||
if (
|
||||
[
|
||||
'delimiter',
|
||||
'encoding',
|
||||
'fromLine',
|
||||
'maxRowCount',
|
||||
'enableBOM',
|
||||
'relaxQuotes',
|
||||
'skipRecordsWithErrors',
|
||||
].includes(option.name)
|
||||
) {
|
||||
newOption = { ...option, displayOptions: { show: { '/operation': ['csv'] } } };
|
||||
}
|
||||
if (option.name === 'sheetName') {
|
||||
newOption = {
|
||||
...option,
|
||||
displayOptions: { show: { '/operation': ['ods', 'xls', 'xlsx'] } },
|
||||
description: 'Name of the sheet to read from in the spreadsheet',
|
||||
};
|
||||
}
|
||||
if (option.name === 'range') {
|
||||
newOption = {
|
||||
...option,
|
||||
displayOptions: { show: { '/operation': ['ods', 'xls', 'xlsx'] } },
|
||||
};
|
||||
}
|
||||
if (['includeEmptyCells', 'headerRow'].includes(option.name)) {
|
||||
newOption = {
|
||||
...option,
|
||||
displayOptions: { show: { '/operation': ['ods', 'xls', 'xlsx', 'csv', 'html'] } },
|
||||
};
|
||||
}
|
||||
return newOption;
|
||||
});
|
||||
}
|
||||
return newProperty;
|
||||
});
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
fileFormatProperty: string,
|
||||
options?: fromFile.FromFileOptions,
|
||||
) {
|
||||
const returnData: INodeExecutionData[] = await fromFile.execute.call(
|
||||
this,
|
||||
items,
|
||||
fileFormatProperty,
|
||||
options,
|
||||
);
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.9375 2C0.419733 2 0 2.41973 0 2.9375V37.3221C0 37.8399 0.419733 38.2596 0.9375 38.2596H26.2154C26.7332 38.2596 27.1529 37.8399 27.1529 37.3221L27.1529 30.6719L16.7969 30.6719C14.8984 30.6719 13.3594 29.1329 13.3594 27.2344V25.3594C13.3594 23.4609 14.8984 21.9219 16.7969 21.9219H27.1529L27.1529 15.7821H14.3083C13.7906 15.7821 13.3708 15.3624 13.3708 14.8446V2H0.9375Z" fill="#6275AD"/>
|
||||
<path d="M16.0271 2C15.9408 2 15.8708 2.06996 15.8708 2.15625V12.8134C15.8708 13.0723 16.0807 13.2821 16.3396 13.2821H26.9967C27.083 13.2821 27.1529 13.2122 27.1529 13.1259L27.1529 12.6171C27.1529 12.3688 27.0545 12.1307 26.8791 11.955L17.2225 2.27538C17.0466 2.09908 16.8079 2 16.5588 2H16.0271Z" fill="#6275AD"/>
|
||||
<path d="M29.7642 34.6503C29.034 33.916 29.0374 32.7288 29.7716 31.9986L33.6197 28.1719L16.7969 28.1719C16.2791 28.1719 15.8594 27.7521 15.8594 27.2344V25.3594C15.8594 24.8416 16.2791 24.4219 16.7969 24.4219L33.5412 24.4219L29.7716 20.6733C29.0374 19.9431 29.034 18.7559 29.7642 18.0216C30.4944 17.2874 31.6816 17.284 32.4159 18.0142L39.4471 25.0064C39.801 25.3583 40 25.8368 40 26.3359C40 26.835 39.801 27.3135 39.4471 27.6655L32.4159 34.6576C31.6816 35.3878 30.4944 35.3845 29.7642 34.6503Z" fill="#6275AD"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.9375 2C0.419733 2 0 2.41973 0 2.9375V37.3221C0 37.8399 0.419733 38.2596 0.9375 38.2596H26.2154C26.7332 38.2596 27.1529 37.8399 27.1529 37.3221L27.1529 30.6719L16.7969 30.6719C14.8984 30.6719 13.3594 29.1329 13.3594 27.2344V25.3594C13.3594 23.4609 14.8984 21.9219 16.7969 21.9219H27.1529L27.1529 15.7821H14.3083C13.7906 15.7821 13.3708 15.3624 13.3708 14.8446V2H0.9375Z" fill="#353F6E"/>
|
||||
<path d="M16.0271 2C15.9408 2 15.8708 2.06996 15.8708 2.15625V12.8134C15.8708 13.0723 16.0807 13.2821 16.3396 13.2821H26.9967C27.083 13.2821 27.1529 13.2122 27.1529 13.1259L27.1529 12.6171C27.1529 12.3688 27.0545 12.1307 26.8791 11.955L17.2225 2.27538C17.0466 2.09908 16.8079 2 16.5588 2H16.0271Z" fill="#353F6E"/>
|
||||
<path d="M29.7642 34.6503C29.034 33.916 29.0374 32.7288 29.7716 31.9986L33.6197 28.1719L16.7969 28.1719C16.2791 28.1719 15.8594 27.7521 15.8594 27.2344V25.3594C15.8594 24.8416 16.2791 24.4219 16.7969 24.4219L33.5412 24.4219L29.7716 20.6733C29.0374 19.9431 29.034 18.7559 29.7642 18.0216C30.4944 17.2874 31.6816 17.284 32.4159 18.0142L39.4471 25.0064C39.801 25.3583 40 25.8368 40 26.3359C40 26.835 39.801 27.3135 39.4471 27.6655L32.4159 34.6576C31.6816 35.3878 30.4944 35.3845 29.7642 34.6503Z" fill="#353F6E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,5 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
describe('ExtractFromFile', () => {
|
||||
new NodeTestHarness().setupTests();
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [-200, -40],
|
||||
"id": "64686c0a-64a4-4a33-9e70-038c9d23c25b",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"binaryPropertyName": "myfile",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [420, -40],
|
||||
"id": "aaac18d3-1e99-4c47-9de8-2dc8bf95abd7",
|
||||
"name": "Extract from File"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "39b5f05f-85c5-499a-86d6-591d6440f147",
|
||||
"name": "text",
|
||||
"value": "Karlovy Vary město lázní Příliš žluťoučký kůň úpěl ďábelské ódy.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [-20, -40],
|
||||
"id": "e74c67f1-171a-42a7-be12-b6687935988f",
|
||||
"name": "Edit Fields"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toText",
|
||||
"sourceProperty": "text",
|
||||
"binaryPropertyName": "myfile",
|
||||
"options": {
|
||||
"encoding": "windows1256"
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [180, -40],
|
||||
"id": "77ddd6d4-1d75-4ad2-8301-e70213d8371e",
|
||||
"name": "windows1256"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract from File": {
|
||||
"main": [[]]
|
||||
},
|
||||
"Edit Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "windows1256",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"windows1256": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract from File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.readWriteFile",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.readwritefile/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": ["Binary", "File", "Text", "Open", "Import", "Save", "Export", "Disk", "Transfer"],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Files"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import * as read from './actions/read.operation';
|
||||
import * as write from './actions/write.operation';
|
||||
|
||||
export class ReadWriteFile implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Read/Write Files from Disk',
|
||||
name: 'readWriteFile',
|
||||
icon: 'file:readWriteFile.svg',
|
||||
group: ['input'],
|
||||
version: [1, 1.1],
|
||||
description: 'Read or write files from the computer that runs n8n',
|
||||
defaults: {
|
||||
name: 'Read/Write Files from Disk',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
properties: [
|
||||
{
|
||||
displayName:
|
||||
'Use this node to read and write files on the same computer running n8n. To handle files between different computers please use other nodes (e.g. FTP, HTTP Request, AWS).',
|
||||
name: 'info',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Read File(s) From Disk',
|
||||
value: 'read',
|
||||
description: 'Retrieve one or more files from the computer that runs n8n',
|
||||
action: 'Read File(s) From Disk',
|
||||
},
|
||||
{
|
||||
name: 'Write File to Disk',
|
||||
value: 'write',
|
||||
description: 'Create a binary file on the computer that runs n8n',
|
||||
action: 'Write File to Disk',
|
||||
},
|
||||
],
|
||||
default: 'read',
|
||||
},
|
||||
...read.description,
|
||||
...write.description,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
const operation = this.getNodeParameter('operation', 0, 'read');
|
||||
const items = this.getInputData();
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
if (operation === 'read') {
|
||||
returnData = await read.execute.call(this, items);
|
||||
}
|
||||
|
||||
if (operation === 'write') {
|
||||
returnData = await write.execute.call(this, items);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import glob from 'fast-glob';
|
||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { errorMapper, normalizeFileSelector } from '../helpers/utils';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'File(s) Selector',
|
||||
name: 'fileSelector',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. /home/user/Pictures/**/*.png',
|
||||
hint: 'Supports patterns, learn more <a href="https://github.com/micromatch/picomatch#basic-globbing" target="_blank">here</a>',
|
||||
description:
|
||||
"Specify a file's path or path pattern to read multiple files. Always use forward-slashes for path separator even on Windows.",
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'File Extension',
|
||||
name: 'fileExtension',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. zip',
|
||||
description: 'Extension of the file in the output binary',
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. data.zip',
|
||||
description: 'Name of the file in the output binary',
|
||||
},
|
||||
{
|
||||
displayName: 'Mime Type',
|
||||
name: 'mimeType',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. application/zip',
|
||||
description: 'Mime type of the file in the output binary',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'dataPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
placeholder: 'e.g. data',
|
||||
description: "By default 'data' is used",
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['read'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
let fileSelector;
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
fileSelector = normalizeFileSelector(
|
||||
this.getNodeParameter('fileSelector', itemIndex) as string,
|
||||
);
|
||||
|
||||
const options = this.getNodeParameter('options', itemIndex, {});
|
||||
|
||||
let dataPropertyName = 'data';
|
||||
|
||||
if (options.dataPropertyName) {
|
||||
dataPropertyName = options.dataPropertyName as string;
|
||||
}
|
||||
|
||||
const files = await glob(fileSelector);
|
||||
|
||||
if (files.length === 0 && nodeVersion > 1) {
|
||||
throw new NodeOperationError(this.getNode(), 'No file(s) found', {
|
||||
itemIndex,
|
||||
description: `No file matching the selector "${fileSelector}" found`,
|
||||
});
|
||||
}
|
||||
|
||||
const newItems: INodeExecutionData[] = [];
|
||||
for (const filePath of files) {
|
||||
const stream = await this.helpers.createReadStream(
|
||||
await this.helpers.resolvePath(filePath),
|
||||
);
|
||||
const binaryData = await this.helpers.prepareBinaryData(stream, filePath);
|
||||
|
||||
if (options.fileName !== undefined) {
|
||||
binaryData.fileName = options.fileName as string;
|
||||
}
|
||||
|
||||
if (options.fileExtension !== undefined) {
|
||||
binaryData.fileExtension = options.fileExtension as string;
|
||||
}
|
||||
|
||||
if (options.mimeType !== undefined) {
|
||||
binaryData.mimeType = options.mimeType as string;
|
||||
}
|
||||
|
||||
newItems.push({
|
||||
binary: {
|
||||
[dataPropertyName]: binaryData,
|
||||
},
|
||||
json: {
|
||||
mimeType: binaryData.mimeType,
|
||||
fileType: binaryData.fileType,
|
||||
fileName: binaryData.fileName,
|
||||
fileExtension: binaryData.fileExtension,
|
||||
fileSize: binaryData.fileSize,
|
||||
},
|
||||
pairedItem: {
|
||||
item: itemIndex,
|
||||
},
|
||||
});
|
||||
}
|
||||
returnData.push(...newItems);
|
||||
} catch (error) {
|
||||
const nodeOperationError = errorMapper.call(this, error, itemIndex, {
|
||||
filePath: fileSelector,
|
||||
operation: 'read',
|
||||
});
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: nodeOperationError.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: itemIndex,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex });
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { BINARY_ENCODING, NodeApiError } from 'n8n-workflow';
|
||||
import type { Readable } from 'stream';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { errorMapper } from '../helpers/utils';
|
||||
import { constants } from 'node:fs';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'File Path and Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. /data/example.jpg',
|
||||
description:
|
||||
'Path and name of the file that should be written. Also include the file extension.',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Binary Field',
|
||||
name: 'dataPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
placeholder: 'e.g. data',
|
||||
required: true,
|
||||
hint: 'The name of the input binary field containing the file to be written',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Append',
|
||||
name: 'append',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
"Whether to append to an existing file. While it's commonly used with text files, it's not limited to them, however, it wouldn't be applicable for file types that have a specific structure like most binary formats.",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['write'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
let fileName;
|
||||
|
||||
let item: INodeExecutionData;
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', itemIndex);
|
||||
fileName = this.getNodeParameter('fileName', itemIndex) as string;
|
||||
const options = this.getNodeParameter('options', itemIndex, {});
|
||||
const flag: number = options.append
|
||||
? constants.O_APPEND
|
||||
: constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC;
|
||||
|
||||
item = items[itemIndex];
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
pairedItem: {
|
||||
item: itemIndex,
|
||||
},
|
||||
};
|
||||
Object.assign(newItem.json, item.json);
|
||||
|
||||
const binaryData = this.helpers.assertBinaryData(itemIndex, dataPropertyName);
|
||||
|
||||
let fileContent: Buffer | Readable;
|
||||
if (binaryData.id) {
|
||||
fileContent = await this.helpers.getBinaryStream(binaryData.id);
|
||||
} else {
|
||||
fileContent = Buffer.from(binaryData.data, BINARY_ENCODING);
|
||||
}
|
||||
|
||||
// Write the file to disk
|
||||
await this.helpers.writeContentToFile(
|
||||
await this.helpers.resolvePath(fileName),
|
||||
fileContent,
|
||||
flag,
|
||||
);
|
||||
|
||||
if (item.binary !== undefined) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
newItem.binary = {};
|
||||
Object.assign(newItem.binary, item.binary);
|
||||
}
|
||||
|
||||
// Add the file name to data
|
||||
newItem.json.fileName = fileName;
|
||||
|
||||
returnData.push(newItem);
|
||||
} catch (error) {
|
||||
const nodeOperatioinError = errorMapper.call(this, error, itemIndex, {
|
||||
filePath: fileName,
|
||||
operation: 'write',
|
||||
});
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: nodeOperatioinError.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: itemIndex,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex });
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import path from 'node:path';
|
||||
|
||||
export function errorMapper(
|
||||
this: IExecuteFunctions,
|
||||
error: Error,
|
||||
itemIndex: number,
|
||||
context?: IDataObject,
|
||||
) {
|
||||
let message;
|
||||
let description;
|
||||
|
||||
if (typeof error.message === 'string') {
|
||||
if (error.message.includes('Cannot create a string longer than')) {
|
||||
message = 'The file is too large';
|
||||
description =
|
||||
'The binary file you are attempting to read exceeds 512MB, which is limit when using default binary data mode, try using the filesystem binary mode. More information <a href="https://docs.n8n.io/hosting/scaling/binary-data/" target="_blank">here</a>.';
|
||||
} else if (error.message.includes('EACCES') && context?.operation === 'read') {
|
||||
const path =
|
||||
((error as unknown as IDataObject).path as string) || (context?.filePath as string);
|
||||
message = `You don't have the permissions to access ${path}`;
|
||||
description =
|
||||
"Verify that the path specified in 'File(s) Selector' is correct, or change the file(s) permissions if needed";
|
||||
} else if (error.message.includes('EACCES') && context?.operation === 'write') {
|
||||
const path =
|
||||
((error as unknown as IDataObject).path as string) || (context?.filePath as string);
|
||||
message = `You don't have the permissions to write the file ${path}`;
|
||||
description =
|
||||
"Specify another destination folder in 'File Path and Name', or change the permissions of the parent folder";
|
||||
}
|
||||
}
|
||||
|
||||
return new NodeOperationError(this.getNode(), error, { itemIndex, message, description });
|
||||
}
|
||||
|
||||
export function escapeSpecialCharacters(str: string) {
|
||||
// Escape parentheses
|
||||
str = str.replace(/[()]/g, '\\$&');
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function normalizeFileSelector(fileSelectorRaw: string) {
|
||||
let fileSelector = String(fileSelectorRaw);
|
||||
|
||||
const isWindows = /^[a-zA-Z]:/.test(fileSelector);
|
||||
if (isWindows) {
|
||||
fileSelector = path.win32.normalize(fileSelector).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
fileSelector = escapeSpecialCharacters(fileSelector);
|
||||
|
||||
return fileSelector;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1141_1547)">
|
||||
<path d="M0 12C0 5.37258 5.37258 0 12 0H159V154C159 160.627 164.373 166 171 166H325V242H228.562C210.895 242 194.656 251.705 186.288 267.264L129.203 373.407C125.131 380.978 123 389.44 123 398.037V434H12C5.37257 434 0 428.627 0 422V12Z" fill="#44AA44"/>
|
||||
<path d="M325 134V127.401C325 124.223 323.74 121.175 321.495 118.925L206.369 3.52481C204.118 1.2682 201.061 0 197.873 0H191V134H325Z" fill="#44AA44"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M228.563 274C222.674 274 217.261 277.235 214.472 282.421L172.211 361H492.64L444.67 281.717C441.772 276.927 436.58 274 430.981 274H228.563Z" fill="#44AA44"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155 409C155 400.163 162.163 393 171 393H496C504.837 393 512 400.163 512 409V496C512 504.837 504.837 512 496 512H171C162.163 512 155 504.837 155 496V409ZM397 453C397 466.255 386.255 477 373 477C359.745 477 349 466.255 349 453C349 439.745 359.745 429 373 429C386.255 429 397 439.745 397 453ZM445 477C458.255 477 469 466.255 469 453C469 439.745 458.255 429 445 429C431.745 429 421 439.745 421 453C421 466.255 431.745 477 445 477Z" fill="#44AA44"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1141_1547">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,83 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import fsPromises from 'fs/promises';
|
||||
import type { WorkflowTestData } from 'n8n-workflow';
|
||||
|
||||
describe('Test ReadWriteFile Node', () => {
|
||||
const directory = __dirname.replace(/\\/gi, '/');
|
||||
|
||||
const testHarness = new NodeTestHarness();
|
||||
const workflowData = testHarness.readWorkflowJSON('ReadWriteFile.workflow.json');
|
||||
|
||||
const readFileNode = workflowData.nodes.find((n) => n.name === 'Read from Disk')!;
|
||||
readFileNode.parameters.fileSelector = `${directory}/image.jpg`;
|
||||
|
||||
const writeFileNode = workflowData.nodes.find((n) => n.name === 'Write to Disk')!;
|
||||
writeFileNode.parameters.fileName = `${testHarness.temporaryDir}/image-written.jpg`;
|
||||
|
||||
const realpathSpy = jest.spyOn(fsPromises, 'realpath');
|
||||
realpathSpy.mockImplementation(async (path) => path as string);
|
||||
|
||||
const tests: WorkflowTestData[] = [
|
||||
{
|
||||
description: 'nodes/Files/ReadWriteFile/test/ReadWriteFile.workflow.json',
|
||||
input: {
|
||||
workflowData,
|
||||
},
|
||||
output: {
|
||||
assertBinaryData: true,
|
||||
nodeData: {
|
||||
'Read from Disk': [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
fileExtension: 'jpg',
|
||||
fileName: 'image.jpg',
|
||||
fileSize: '1.04 kB',
|
||||
fileType: 'image',
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
binary: {
|
||||
data: {
|
||||
mimeType: 'image/jpeg',
|
||||
fileType: 'image',
|
||||
fileExtension: 'jpg',
|
||||
data: '/9j/4AAQSkZJRgABAQEASABIAAD/4QBmRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAAExAAIAAAAQAAAATgAAAAAAARlJAAAD6AABGUkAAAPocGFpbnQubmV0IDUuMC4xAP/bAEMAIBYYHBgUIBwaHCQiICYwUDQwLCwwYkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxv/bAEMBIiQkMCowXjQ0XsaEcITGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxv/AABEIAB8AOwMBEgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOgqgrXF2zNHJ5aKcD3oNPZ23di/VKG82bkuTh1OMgdaAdOSLtZ6G5ut0iSeWoOAKAdO27NCqUN8oQrcHDqccDrQDpyRNPdRwEKcsx7CobIebPLORwThc0inGMF724jagNpxG4OOM1dIDAgjIPBpkqUOxnR2pmh85pW3nJB9KkNi4yqTssZ6rSNXNX0ehHFfusYDLuI7+tXY4I40ChQcdzQRKcL7Fb7PcQO32cqUY5we1XqZPtH11KsFoFDGYK7sckkZxVqgTnJlEQXMBZYGUoTkZ7VeoH7RvcqwWaIh80K7k5JIq1QJzkyhbMtvdSxMdqnlc1amgjmx5i5I70inNSVpFdrmaWRltkBVerHvUW57B2AUNGxyOaC+VW9xXLVrcGbcjrtkXqKZZxvveeTAL9APSgiooq1ty3RTMj//2Q==',
|
||||
fileName: 'image.jpg',
|
||||
fileSize: '1.04 kB',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
'Write to Disk': [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
fileExtension: 'jpg',
|
||||
fileName: writeFileNode.parameters.fileName,
|
||||
fileSize: '1.04 kB',
|
||||
fileType: 'image',
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
binary: {
|
||||
data: {
|
||||
mimeType: 'image/jpeg',
|
||||
fileType: 'image',
|
||||
fileExtension: 'jpg',
|
||||
data: '/9j/4AAQSkZJRgABAQEASABIAAD/4QBmRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAAExAAIAAAAQAAAATgAAAAAAARlJAAAD6AABGUkAAAPocGFpbnQubmV0IDUuMC4xAP/bAEMAIBYYHBgUIBwaHCQiICYwUDQwLCwwYkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxv/bAEMBIiQkMCowXjQ0XsaEcITGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxv/AABEIAB8AOwMBEgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOgqgrXF2zNHJ5aKcD3oNPZ23di/VKG82bkuTh1OMgdaAdOSLtZ6G5ut0iSeWoOAKAdO27NCqUN8oQrcHDqccDrQDpyRNPdRwEKcsx7CobIebPLORwThc0inGMF724jagNpxG4OOM1dIDAgjIPBpkqUOxnR2pmh85pW3nJB9KkNi4yqTssZ6rSNXNX0ehHFfusYDLuI7+tXY4I40ChQcdzQRKcL7Fb7PcQO32cqUY5we1XqZPtH11KsFoFDGYK7sckkZxVqgTnJlEQXMBZYGUoTkZ7VeoH7RvcqwWaIh80K7k5JIq1QJzkyhbMtvdSxMdqnlc1amgjmx5i5I70inNSVpFdrmaWRltkBVerHvUW57B2AUNGxyOaC+VW9xXLVrcGbcjrtkXqKZZxvveeTAL9APSgiooq1ty3RTMj//2Q==',
|
||||
fileName: 'image.jpg',
|
||||
fileSize: '1.04 kB',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const testData of tests) {
|
||||
testHarness.setupTest(testData);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"meta": {
|
||||
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "01b8609f-a345-41de-80bf-6d84276b5e7a",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
700,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fileSelector": "C:/Test/image.jpg",
|
||||
"options": {}
|
||||
},
|
||||
"id": "a1ea0fd0-cc95-4de2-bc58-bc980cb1d97e",
|
||||
"name": "Read from Disk",
|
||||
"type": "n8n-nodes-base.readWriteFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
920,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "write",
|
||||
"fileName": "C:/Test/image-written.jpg",
|
||||
"options": {}
|
||||
},
|
||||
"id": "94abac52-bd10-4b57-85b0-691c70989137",
|
||||
"name": "Write to Disk",
|
||||
"type": "n8n-nodes-base.readWriteFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1140,
|
||||
320
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Read from Disk",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Read from Disk": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write to Disk",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,43 @@
|
||||
import { escapeSpecialCharacters, normalizeFileSelector } from '../helpers/utils';
|
||||
|
||||
describe('Read/Write Files from Disk', () => {
|
||||
describe('escapeSpecialCharacters', () => {
|
||||
it('should escape parentheses in a string', () => {
|
||||
const input = '/home/michael/Desktop/test(1).txt';
|
||||
const expectedOutput = '/home/michael/Desktop/test\\(1\\).txt';
|
||||
expect(escapeSpecialCharacters(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should not modify strings that do not contain parentheses', () => {
|
||||
const input = '/home/michael/Desktop/test.txt';
|
||||
const expectedOutput = '/home/michael/Desktop/test.txt';
|
||||
expect(escapeSpecialCharacters(input)).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeFileSelector', () => {
|
||||
it('should normalize UNIX file selector with parentheses', () => {
|
||||
const input = '/home/michael/Desktop/test(1).txt';
|
||||
const expectedOutput = '/home/michael/Desktop/test\\(1\\).txt';
|
||||
expect(normalizeFileSelector(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should normalize Windows file selector with \\ and parentheses', () => {
|
||||
const input = 'C:\\Users\\michael\\Desktop\\test(1).txt';
|
||||
const expectedOutput = 'C:/Users/michael/Desktop/test\\(1\\).txt';
|
||||
expect(normalizeFileSelector(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should normalize Windows file selector with \\\\', () => {
|
||||
const input = 'C:\\\\Users\\\\michael\\\\Desktop\\\\test.txt';
|
||||
const expectedOutput = 'C:/Users/michael/Desktop/test.txt';
|
||||
expect(normalizeFileSelector(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should normalize Windows file selector with /', () => {
|
||||
const input = 'C:/Users/michael/Desktop/test.txt';
|
||||
const expectedOutput = 'C:/Users/michael/Desktop/test.txt';
|
||||
expect(normalizeFileSelector(input)).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
describe('Test Convert to File Node', () => {
|
||||
new NodeTestHarness().setupTests();
|
||||
});
|
||||
@@ -0,0 +1,707 @@
|
||||
{
|
||||
"name": "convert to tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "35cce987-aa4f-4738-bfcd-b85098948341",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
680,
|
||||
1100
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "row_number",
|
||||
"type": "numberValue",
|
||||
"numberValue": "2"
|
||||
},
|
||||
{
|
||||
"name": "country",
|
||||
"stringValue": "uk"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"stringValue": "firefox"
|
||||
},
|
||||
{
|
||||
"name": "session_duration",
|
||||
"type": "numberValue",
|
||||
"numberValue": "1"
|
||||
},
|
||||
{
|
||||
"name": "visits",
|
||||
"type": "numberValue",
|
||||
"numberValue": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": "none",
|
||||
"options": {}
|
||||
},
|
||||
"id": "13305747-c966-4f46-90b3-ffff6835b714",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
980,
|
||||
880
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "b08b269d-0735-4dc4-b5a7-7870f5e115f9",
|
||||
"name": "Convert to File",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
420
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "html",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c68c209f-771f-4d25-b832-3d55ee97e6ff",
|
||||
"name": "Convert to File1",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
600
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toJson",
|
||||
"options": {}
|
||||
},
|
||||
"id": "2b6f27ed-a4dc-4a29-905f-f0a921ea6ee7",
|
||||
"name": "Convert to File2",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
780
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toJson",
|
||||
"mode": "each",
|
||||
"options": {}
|
||||
},
|
||||
"id": "cd482d12-7547-4eb6-880b-bd2c31ef06d5",
|
||||
"name": "Convert to File3",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
960
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "xlsx",
|
||||
"options": {}
|
||||
},
|
||||
"id": "1fd693e3-4286-49f4-ba45-70584c1d67f7",
|
||||
"name": "Convert to File5",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
1140
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "636866e9-ca0d-44a3-b27a-90cf33e26485",
|
||||
"name": "Extract From File",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
420
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "html",
|
||||
"options": {}
|
||||
},
|
||||
"id": "5c66b2ea-94b2-4a1b-a34d-acb07fe33e13",
|
||||
"name": "Extract From File1",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
600
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "fromJson",
|
||||
"options": {}
|
||||
},
|
||||
"id": "a03752f0-e3bb-4dd3-9aae-dc4d1471c281",
|
||||
"name": "Extract From File2",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
780
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "fromJson",
|
||||
"options": {}
|
||||
},
|
||||
"id": "eb10c006-60d7-4842-b9e5-a364f42dd1ab",
|
||||
"name": "Extract From File3",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
960
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "xlsx",
|
||||
"options": {}
|
||||
},
|
||||
"id": "64c98172-2a77-4e83-b19b-232899df8113",
|
||||
"name": "Extract From File4",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
1140
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "xls",
|
||||
"options": {}
|
||||
},
|
||||
"id": "edbfd57e-d36b-470d-9e8d-9c7f67c131b4",
|
||||
"name": "Convert to File6",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
1320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "xls",
|
||||
"options": {}
|
||||
},
|
||||
"id": "86713806-8dc6-45ec-a710-e80b839ec193",
|
||||
"name": "Extract From File5",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
1320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "base64",
|
||||
"stringValue": "VGhpcyBpcyB0ZXh0IGNvbnZlcnRlZCB0byBiYXNlIDY0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": "none",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c205d380-2459-4d16-bb56-f862c53c25de",
|
||||
"name": "Edit Fields1",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
980,
|
||||
1060
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "toBinary",
|
||||
"sourceProperty": "base64",
|
||||
"options": {}
|
||||
},
|
||||
"id": "3f5fe04c-63cf-47d0-a7ca-d427b95a0c52",
|
||||
"name": "Convert to File7",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
1880
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"options": {}
|
||||
},
|
||||
"id": "dbb4eae0-f0ee-4453-aed7-7d8322974c94",
|
||||
"name": "Extract From File6",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
1880
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "ods",
|
||||
"options": {}
|
||||
},
|
||||
"id": "e3bf810d-7795-4663-82d1-768f76adc0d9",
|
||||
"name": "Convert to File8",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
1520
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "ods",
|
||||
"options": {}
|
||||
},
|
||||
"id": "7713d273-2d51-4e3e-8ac9-9a4e6013c690",
|
||||
"name": "Extract From File7",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
1520
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "rtf",
|
||||
"options": {}
|
||||
},
|
||||
"id": "631b29cb-dcde-42cc-a514-45622923dab6",
|
||||
"name": "Convert to File9",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
1700
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "rtf",
|
||||
"options": {}
|
||||
},
|
||||
"id": "168b6586-c89d-4b0e-880e-4ddb8ea7cb2f",
|
||||
"name": "Extract From File8",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
1700
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "iCal",
|
||||
"start": "2024-01-03T00:00:00",
|
||||
"end": "2024-01-04T00:00:00",
|
||||
"allDay": true,
|
||||
"additionalFields": {
|
||||
"description": "event description"
|
||||
}
|
||||
},
|
||||
"id": "2ba4acd9-2677-4b25-9379-48433ac5e9cc",
|
||||
"name": "Convert to File10",
|
||||
"type": "n8n-nodes-base.convertToFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1380,
|
||||
2100
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "fromIcs",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c0179d40-7de0-4e42-a6bf-97c5bb764665",
|
||||
"name": "Extract From File9",
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1600,
|
||||
2100
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "description",
|
||||
"stringValue": "={{ $json.data.events[0].description }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": "none",
|
||||
"options": {}
|
||||
},
|
||||
"id": "8ee98090-f3b0-41d5-8122-d27e5559738f",
|
||||
"name": "Edit Fields2",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1820,
|
||||
2100
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Extract From File": [
|
||||
{
|
||||
"json": {
|
||||
"row_number": "2",
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": "1",
|
||||
"visits": "1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File1": [
|
||||
{
|
||||
"json": {
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File2": [
|
||||
{
|
||||
"json": {
|
||||
"data": [
|
||||
{
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File3": [
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File4": [
|
||||
{
|
||||
"json": {
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File5": [
|
||||
{
|
||||
"json": {
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File7": [
|
||||
{
|
||||
"json": {
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File6": [
|
||||
{
|
||||
"json": {
|
||||
"data": "This is text converted to base 64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Extract From File8": [
|
||||
{
|
||||
"json": {
|
||||
"row_number": 2,
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Edit Fields2": [
|
||||
{
|
||||
"json": {
|
||||
"description": "event description"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Edit Fields1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File10",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Convert to File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File5",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File6",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File8",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Convert to File9",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File3": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File5": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File6": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File5",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File7": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File6",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Convert to File7",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File8": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File7",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File9": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File8",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Convert to File10": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract From File9",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract From File9": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "3287cce3-f02f-45df-9d3b-2f116852c1fb",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"id": "ZzoOtOee7hxaNcmp",
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user