mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
fix(HTML Node): Escape data path value in JSON Property (#8441)
This commit is contained in:
parent
7c49004018
commit
fc5c562785
|
@ -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],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...extractionValuesCollection,
|
||||
default: {
|
||||
values: [
|
||||
{
|
||||
key: '',
|
||||
cssSelector: '',
|
||||
returnValue: 'text',
|
||||
returnArray: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['extractHtmlContent'],
|
||||
'@version': [{ _cnd: { gt: 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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
|
@ -329,6 +355,7 @@ export class Html implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
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 = {
|
||||
|
|
|
@ -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)];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue