diff --git a/packages/nodes-base/nodes/Html/Html.node.ts b/packages/nodes-base/nodes/Html/Html.node.ts index 10d3e75e9ce80..f83ebb9318361 100644 --- a/packages/nodes-base/nodes/Html/Html.node.ts +++ b/packages/nodes-base/nodes/Html/Html.node.ts @@ -5,12 +5,15 @@ import type { INodeType, INodeTypeDescription, IDataObject, + INodeProperties, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import { placeholder } from './placeholder'; import { getValue } from './utils'; import type { IValueData } from './types'; -import { getResolvables } from '@utils/utilities'; +import { getResolvables, sanitazeDataPathKey } from '@utils/utilities'; + +import get from 'lodash/get'; export const capitalizeHeader = (header: string, capitalize?: boolean) => { if (!capitalize) return header; @@ -21,13 +24,97 @@ export const capitalizeHeader = (header: string, capitalize?: boolean) => { .join(' '); }; +const extractionValuesCollection: INodeProperties = { + displayName: 'Extraction Values', + name: 'extractionValues', + placeholder: 'Add Value', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'values', + displayName: 'Values', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'The key under which the extracted value should be saved', + }, + { + displayName: 'CSS Selector', + name: 'cssSelector', + type: 'string', + default: '', + placeholder: '.price', + description: 'The CSS selector to use', + }, + { + displayName: 'Return Value', + name: 'returnValue', + type: 'options', + options: [ + { + name: 'Attribute', + value: 'attribute', + description: 'Get an attribute value like "class" from an element', + }, + { + name: 'HTML', + value: 'html', + description: 'Get the HTML the element contains', + }, + { + name: 'Text', + value: 'text', + description: 'Get only the text content of the element', + }, + { + name: 'Value', + value: 'value', + description: 'Get value of an input, select or textarea', + }, + ], + default: 'text', + description: 'What kind of data should be returned', + }, + { + displayName: 'Attribute', + name: 'attribute', + type: 'string', + displayOptions: { + show: { + returnValue: ['attribute'], + }, + }, + default: '', + placeholder: 'class', + description: 'The name of the attribute to return the value off', + }, + { + displayName: 'Return Array', + name: 'returnArray', + type: 'boolean', + default: false, + description: + 'Whether to return the values as an array so if multiple ones get found they also get returned separately. If not set all will be returned as a single string.', + }, + ], + }, + ], +}; + export class Html implements INodeType { description: INodeTypeDescription = { displayName: 'HTML', name: 'html', icon: 'file:html.svg', group: ['transform'], - version: 1, + version: [1, 1.1], subtitle: '={{ $parameter["operation"] }}', description: 'Work with HTML', defaults: { @@ -143,94 +230,33 @@ export class Html implements INodeType { 'Name of the JSON property in which the HTML to extract the data from can be found. The property can either contain a string or an array of strings.', }, { - displayName: 'Extraction Values', - name: 'extractionValues', - placeholder: 'Add Value', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, + ...extractionValuesCollection, displayOptions: { show: { operation: ['extractHtmlContent'], + '@version': [1], }, }, - default: {}, - options: [ - { - name: 'values', - displayName: 'Values', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - description: 'The key under which the extracted value should be saved', - }, - { - displayName: 'CSS Selector', - name: 'cssSelector', - type: 'string', - default: '', - placeholder: '.price', - description: 'The CSS selector to use', - }, - { - displayName: 'Return Value', - name: 'returnValue', - type: 'options', - options: [ - { - name: 'Attribute', - value: 'attribute', - description: 'Get an attribute value like "class" from an element', - }, - { - name: 'HTML', - value: 'html', - description: 'Get the HTML the element contains', - }, - { - name: 'Text', - value: 'text', - description: 'Get only the text content of the element', - }, - { - name: 'Value', - value: 'value', - description: 'Get value of an input, select or textarea', - }, - ], - default: 'text', - description: 'What kind of data should be returned', - }, - { - displayName: 'Attribute', - name: 'attribute', - type: 'string', - displayOptions: { - show: { - returnValue: ['attribute'], - }, - }, - default: '', - placeholder: 'class', - description: 'The name of the attribute to return the value off', - }, - { - displayName: 'Return Array', - name: 'returnArray', - type: 'boolean', - default: false, - description: - 'Whether to return the values as an array so if multiple ones get found they also get returned separately. If not set all will be returned as a single string.', - }, - ], + }, + { + ...extractionValuesCollection, + default: { + values: [ + { + key: '', + cssSelector: '', + returnValue: 'text', + returnArray: false, + }, + ], + }, + displayOptions: { + show: { + operation: ['extractHtmlContent'], + '@version': [{ _cnd: { gt: 1 } }], }, - ], + }, }, - { displayName: 'Options', name: 'options', @@ -329,6 +355,7 @@ export class Html implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const operation = this.getNodeParameter('operation', 0); + const nodeVersion = this.getNode().typeVersion; if (operation === 'convertToHtmlTable' && items.length) { let table = ''; @@ -467,14 +494,27 @@ export class Html implements INodeType { let htmlArray: string[] | string = []; if (sourceData === 'json') { - if (item.json[dataPropertyName] === undefined) { - throw new NodeOperationError( - this.getNode(), - `No property named "${dataPropertyName}" exists!`, - { itemIndex }, - ); + if (nodeVersion === 1) { + const key = sanitazeDataPathKey(item.json, dataPropertyName); + if (item.json[key] === undefined) { + throw new NodeOperationError( + this.getNode(), + `No property named "${dataPropertyName}" exists!`, + { itemIndex }, + ); + } + htmlArray = item.json[key] as string; + } else { + const value = get(item.json, dataPropertyName); + if (value === undefined) { + throw new NodeOperationError( + this.getNode(), + `No property named "${dataPropertyName}" exists!`, + { itemIndex }, + ); + } + htmlArray = value as string; } - htmlArray = item.json[dataPropertyName] as string; } else { this.helpers.assertBinaryData(itemIndex, dataPropertyName); const binaryDataBuffer = await this.helpers.getBinaryDataBuffer( @@ -489,7 +529,7 @@ export class Html implements INodeType { htmlArray = [htmlArray]; } - for (const html of htmlArray as string[]) { + for (const html of htmlArray) { const $ = cheerio.load(html); const newItem: INodeExecutionData = { diff --git a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts index 31943b0fbf37e..c57fafa587504 100644 --- a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts @@ -17,7 +17,7 @@ import set from 'lodash/set'; import get from 'lodash/get'; import unset from 'lodash/unset'; -import { getResolvables } from '../../../../utils/utilities'; +import { getResolvables, sanitazeDataPathKey } from '../../../../utils/utilities'; import type { SetNodeOptions, SetField } from './interfaces'; import { INCLUDE } from './interfaces'; @@ -35,28 +35,15 @@ const configureFieldHelper = (dotNotation?: boolean) => { }, }; } else { - const sanitazeKey = (item: IDataObject, key: string) => { - if (item[key] !== undefined) { - return key; - } - - if (key.startsWith("['") && key.endsWith("']")) { - key = key.slice(2, -2); - if (item[key] !== undefined) { - return key; - } - } - return key; - }; return { set: (item: IDataObject, key: string, value: IDataObject) => { - item[sanitazeKey(item, key)] = value; + item[sanitazeDataPathKey(item, key)] = value; }, get: (item: IDataObject, key: string) => { - return item[sanitazeKey(item, key)]; + return item[sanitazeDataPathKey(item, key)]; }, unset: (item: IDataObject, key: string) => { - delete item[sanitazeKey(item, key)]; + delete item[sanitazeDataPathKey(item, key)]; }, }; } diff --git a/packages/nodes-base/utils/utilities.ts b/packages/nodes-base/utils/utilities.ts index ac07ad65d7594..deed6b9c84776 100644 --- a/packages/nodes-base/utils/utilities.ts +++ b/packages/nodes-base/utils/utilities.ts @@ -326,3 +326,20 @@ export function preparePairedItemDataArray( if (Array.isArray(pairedItem)) return pairedItem; return [pairedItem]; } + +export const sanitazeDataPathKey = (item: IDataObject, key: string) => { + if (item[key] !== undefined) { + return key; + } + + if ( + (key.startsWith("['") && key.endsWith("']")) || + (key.startsWith('["') && key.endsWith('"]')) + ) { + key = key.slice(2, -2); + if (item[key] !== undefined) { + return key; + } + } + return key; +};