From 0a31b8e2b4aab8d74d80f76598900109fe19a0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Tue, 18 Jul 2023 20:07:29 +0200 Subject: [PATCH] feat(Read PDF Node): Replace pdf-parse with pdfjs, and add support for streaming and encrypted PDFs (#6640) --- packages/core/src/NodeExecuteFunctions.ts | 5 + packages/editor-ui/package.json | 1 - .../nodes-base/nodes/ReadPdf/ReadPDF.node.ts | 99 ++++++++++++++---- .../test/ReadPDF-encrypted.workflow.json | 87 +++++++++++++++ .../nodes/ReadPdf/test/ReadPDF.test.ts | 61 ++--------- .../nodes/ReadPdf/test/ReadPDF.workflow.json | 74 ++++++++----- .../nodes/ReadPdf/test/sample-encrypted.pdf | Bin 0 -> 18861 bytes packages/nodes-base/package.json | 3 +- packages/nodes-base/test/nodes/Helpers.ts | 17 +++ packages/workflow/src/Interfaces.ts | 1 + pnpm-lock.yaml | 50 ++++----- 11 files changed, 267 insertions(+), 131 deletions(-) create mode 100644 packages/nodes-base/nodes/ReadPdf/test/ReadPDF-encrypted.workflow.json create mode 100644 packages/nodes-base/nodes/ReadPdf/test/sample-encrypted.pdf diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 2c2541285c..f49bc046a9 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -861,6 +861,10 @@ async function httpRequest( return result.data; } +export function getBinaryPath(binaryDataId: string): string { + return BinaryDataManager.getInstance().getBinaryPath(binaryDataId); +} + /** * Returns binary file metadata */ @@ -2262,6 +2266,7 @@ const getNodeHelperFunctions = ({ const getBinaryHelperFunctions = ({ executionId, }: IWorkflowExecuteAdditionalData): BinaryHelperFunctions => ({ + getBinaryPath, getBinaryStream, getBinaryMetadata, binaryToBuffer, diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 520a991704..fdf1dc01a1 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -85,7 +85,6 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/user-event": "^14.4.3", "@testing-library/vue": "^5.8.3", - "@types/canvas-confetti": "^1.6.0", "@types/dateformat": "^3.0.0", "@types/file-saver": "^2.0.1", "@types/humanize-duration": "^3.27.1", diff --git a/packages/nodes-base/nodes/ReadPdf/ReadPDF.node.ts b/packages/nodes-base/nodes/ReadPdf/ReadPDF.node.ts index 6626c94e53..0379f06293 100644 --- a/packages/nodes-base/nodes/ReadPdf/ReadPDF.node.ts +++ b/packages/nodes-base/nodes/ReadPdf/ReadPDF.node.ts @@ -1,12 +1,32 @@ -import type { - IExecuteFunctions, - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeDescription, +import { + BINARY_ENCODING, + type IExecuteFunctions, + type INodeExecutionData, + type INodeType, + type INodeTypeDescription, } from 'n8n-workflow'; -import pdf from 'pdf-parse'; +import { getDocument as readPDF, version as pdfJsVersion } from 'pdfjs-dist'; + +type Document = Awaited>['promise']>; +type Page = Awaited>>; +type TextContent = Awaited>; + +const parseText = (textContent: TextContent) => { + let lastY = undefined; + const text = []; + for (const item of textContent.items) { + if ('str' in item) { + if (lastY == item.transform[5] || !lastY) { + text.push(item.str); + } else { + text.push(`\n${item.str}`); + } + lastY = item.transform[5]; + } + } + return text.join(''); +}; export class ReadPDF implements INodeType { description: INodeTypeDescription = { @@ -32,6 +52,26 @@ export class ReadPDF implements INodeType { required: true, description: 'Name of the binary property from which to read the PDF file', }, + { + displayName: 'Encrypted', + name: 'encrypted', + type: 'boolean', + default: false, + required: true, + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { password: true }, + default: '', + description: 'Password to decrypt the PDF file with', + displayOptions: { + show: { + encrypted: [true], + }, + }, + }, ], }; @@ -40,27 +80,50 @@ export class ReadPDF implements INodeType { const returnData: INodeExecutionData[] = []; const length = items.length; - let item: INodeExecutionData; for (let itemIndex = 0; itemIndex < length; itemIndex++) { try { - item = items[itemIndex]; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex); + const binaryData = this.helpers.assertBinaryData(itemIndex, binaryPropertyName); - if (item.binary === undefined) { - item.binary = {}; + const params: { password?: string; url?: URL; data?: ArrayBuffer } = {}; + + if (this.getNodeParameter('encrypted', itemIndex) === true) { + params.password = this.getNodeParameter('password', itemIndex) as string; } - const binaryDataBuffer = await this.helpers.getBinaryDataBuffer( - itemIndex, - binaryPropertyName, - ); - returnData.push({ - binary: item.binary, + if (binaryData.id) { + const binaryPath = this.helpers.getBinaryPath(binaryData.id); + params.url = new URL(`file://${binaryPath}`); + } else { + params.data = Buffer.from(binaryData.data, BINARY_ENCODING).buffer; + } - json: (await pdf(binaryDataBuffer)) as unknown as IDataObject, + const document = await readPDF(params).promise; + const { info, metadata } = await document + .getMetadata() + .catch(() => ({ info: null, metadata: null })); + + const pages = []; + for (let i = 1; i <= document.numPages; i++) { + const page = await document.getPage(i); + const text = await page.getTextContent().then(parseText); + pages.push(text); + } + + returnData.push({ + binary: items[itemIndex].binary, + json: { + numpages: document.numPages, + numrender: document.numPages, + info, + metadata: metadata?.getAll(), + text: pages.join('\n\n'), + version: pdfJsVersion, + }, }); } catch (error) { + console.log(error); if (this.continueOnFail()) { returnData.push({ json: { diff --git a/packages/nodes-base/nodes/ReadPdf/test/ReadPDF-encrypted.workflow.json b/packages/nodes-base/nodes/ReadPdf/test/ReadPDF-encrypted.workflow.json new file mode 100644 index 0000000000..7258819ead --- /dev/null +++ b/packages/nodes-base/nodes/ReadPdf/test/ReadPDF-encrypted.workflow.json @@ -0,0 +1,87 @@ +{ + "nodes": [ + { + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "parameters": {}, + "position": [660, 580] + }, + { + "name": "Read sample-encrypted.pdf", + "type": "n8n-nodes-base.readBinaryFile", + "typeVersion": 1, + "parameters": { + "filePath": "C:\\Test\\sample-encrypted.pdf" + }, + "position": [880, 780] + }, + { + "name": "Read PDF (encrypted)", + "type": "n8n-nodes-base.readPDF", + "typeVersion": 1, + "parameters": { + "encrypted": true, + "password": "ReaderPassword" + }, + "position": [1100, 780] + } + ], + "pinData": { + "Read PDF (encrypted)": [ + { + "binary": { + "data": { + "fileExtension": "pdf", + "fileName": "sample-encrypted.pdf", + "fileSize": "18.9 kB", + "mimeType": "application/pdf" + } + }, + "json": { + "numpages": 1, + "numrender": 1, + "info": { + "PDFFormatVersion": "1.7", + "Language": null, + "EncryptFilterName": "Standard", + "IsLinearized": false, + "IsAcroFormPresent": false, + "IsXFAPresent": false, + "IsCollectionPresent": false, + "IsSignaturesPresent": false, + "ModDate": "D:20230210122750Z", + "Producer": "iLovePDF", + "Title": "sample" + }, + "text": "N8N\nSample PDF\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor\ninvidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et\njusto duo dolores et ea rebum.", + "version": "2.16.105" + } + } + ] + }, + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Read sample-encrypted.pdf", + "type": "main", + "index": 0 + } + ] + ] + }, + "Read sample-encrypted.pdf": { + "main": [ + [ + { + "node": "Read PDF (encrypted)", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.test.ts b/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.test.ts index efe6c44f11..cd91f69a1d 100644 --- a/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.test.ts +++ b/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.test.ts @@ -1,60 +1,11 @@ -/* eslint-disable @typescript-eslint/no-loop-func */ -import * as Helpers from '@test/nodes/Helpers'; -import type { WorkflowTestData } from '@test/nodes/types'; -import { executeWorkflow } from '@test/nodes/ExecuteWorkflow'; -import path from 'path'; +import { getWorkflowFilenames, initBinaryDataManager, testWorkflows } from '@test/nodes/Helpers'; describe('Test Read PDF Node', () => { - beforeEach(async () => { - await Helpers.initBinaryDataManager(); + const workflows = getWorkflowFilenames(__dirname); + + beforeAll(async () => { + await initBinaryDataManager(); }); - const workflow = Helpers.readJsonFileSync('nodes/ReadPdf/test/ReadPDF.workflow.json'); - const node = workflow.nodes.find((n: any) => n.name === 'Read Binary File'); - node.parameters.filePath = path.join(__dirname, 'sample.pdf'); - - const testData: WorkflowTestData = { - description: 'nodes/ReadPdf/test/ReadPDF.workflow.json', - input: { - workflowData: workflow, - }, - output: { - nodeData: { - 'Read PDF': [ - [ - { - json: { - numpages: 1, - numrender: 1, - info: { - PDFFormatVersion: '1.4', - IsAcroFormPresent: false, - IsXFAPresent: false, - Title: 'sample', - Producer: 'iLovePDF', - ModDate: 'D:20230210122750Z', - }, - metadata: null, - text: '\n\nN8N\nSample PDF\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor\ninvidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et\njusto duo dolores et ea rebum.', - version: '1.10.100', - }, - }, - ], - ], - }, - }, - }; - - const nodeTypes = Helpers.setup(testData); - - test(testData.description, async () => { - const { result } = await executeWorkflow(testData, nodeTypes); - const resultNodeData = Helpers.getResultNodeData(result, testData); - - // delete binary data because we test against json only - delete resultNodeData[0].resultData[0]![0].binary; - expect(resultNodeData[0].resultData).toEqual(testData.output.nodeData['Read PDF']); - - expect(result.finished).toEqual(true); - }); + testWorkflows(workflows); }); diff --git a/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.workflow.json b/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.workflow.json index 9d6aaf0c1f..4a56640108 100644 --- a/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.workflow.json +++ b/packages/nodes-base/nodes/ReadPdf/test/ReadPDF.workflow.json @@ -1,47 +1,75 @@ { - "name": "Read PDF node unit test", "nodes": [ { - "parameters": {}, - "id": "0c9db33c-dd15-4088-9d12-b9f3b8f1fa96", "name": "When clicking \"Execute Workflow\"", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, - "position": [960, 540] - }, - { "parameters": {}, - "id": "86abdc3b-206d-4b67-a37f-6b67b6bd3bbc", - "name": "Read PDF", - "type": "n8n-nodes-base.readPDF", - "typeVersion": 1, - "position": [1400, 540] + "position": [660, 580] }, { + "name": "Read sample.pdf", + "type": "n8n-nodes-base.readBinaryFile", + "typeVersion": 1, "parameters": { "filePath": "C:\\Test\\sample.pdf" }, - "id": "2f6d241e-44a4-4213-b49a-166201946a89", - "name": "Read Binary File", - "type": "n8n-nodes-base.readBinaryFile", + "position": [880, 580] + }, + { + "name": "Read PDF", + "type": "n8n-nodes-base.readPDF", "typeVersion": 1, - "position": [1180, 540] + "parameters": {}, + "position": [1100, 580] } ], - "pinData": {}, + "pinData": { + "Read PDF": [ + { + "binary": { + "data": { + "fileExtension": "pdf", + "fileName": "sample.pdf", + "fileSize": "17.8 kB", + "mimeType": "application/pdf" + } + }, + "json": { + "numpages": 1, + "numrender": 1, + "info": { + "PDFFormatVersion": "1.4", + "Language": null, + "EncryptFilterName": null, + "IsLinearized": false, + "IsAcroFormPresent": false, + "IsXFAPresent": false, + "IsCollectionPresent": false, + "IsSignaturesPresent": false, + "Title": "sample", + "Producer": "iLovePDF", + "ModDate": "D:20230210122750Z" + }, + "text": "N8N\nSample PDF\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor\ninvidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et\njusto duo dolores et ea rebum.", + "version": "2.16.105" + } + } + ] + }, "connections": { "When clicking \"Execute Workflow\"": { "main": [ [ { - "node": "Read Binary File", + "node": "Read sample.pdf", "type": "main", "index": 0 } ] ] }, - "Read Binary File": { + "Read sample.pdf": { "main": [ [ { @@ -52,13 +80,5 @@ ] ] } - }, - "active": false, - "settings": {}, - "versionId": "9802b48d-727a-40ef-ad87-d544a9a648a7", - "id": "188", - "meta": { - "instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c" - }, - "tags": [] + } } diff --git a/packages/nodes-base/nodes/ReadPdf/test/sample-encrypted.pdf b/packages/nodes-base/nodes/ReadPdf/test/sample-encrypted.pdf new file mode 100644 index 0000000000000000000000000000000000000000..52d7fe80e8a399b6f61968ef68fd1a6c8d33c522 GIT binary patch literal 18861 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3fcae99d34..8513350c64 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -770,7 +770,6 @@ "@types/mssql": "^6.0.2", "@types/node-ssh": "^7.0.1", "@types/nodemailer": "^6.4.0", - "@types/pdf-parse": "^1.1.1", "@types/promise-ftp": "^1.3.4", "@types/redis": "^2.8.11", "@types/request-promise-native": "~1.0.15", @@ -826,7 +825,7 @@ "node-ssh": "^12.0.0", "nodemailer": "^6.7.1", "otpauth": "^9.1.1", - "pdf-parse": "^1.1.1", + "pdfjs-dist": "^2.16.105", "pg": "^8.3.0", "pg-promise": "^10.5.8", "pretty-bytes": "^5.6.0", diff --git a/packages/nodes-base/test/nodes/Helpers.ts b/packages/nodes-base/test/nodes/Helpers.ts index b30e6acd34..dc359391dd 100644 --- a/packages/nodes-base/test/nodes/Helpers.ts +++ b/packages/nodes-base/test/nodes/Helpers.ts @@ -321,6 +321,15 @@ export const equalityTest = async (testData: WorkflowTestData, types: INodeTypes const resultNodeData = getResultNodeData(result, testData); resultNodeData.forEach(({ nodeName, resultData }) => { const msg = `Equality failed for "${testData.description}" at node "${nodeName}"`; + resultData.forEach((item) => { + item?.forEach(({ binary }) => { + if (binary) { + // @ts-ignore + delete binary.data.data; + delete binary.data.directory; + } + }); + }); return expect(resultData, msg).toEqual(testData.output.nodeData[nodeName]); }); @@ -345,6 +354,14 @@ export const workflowToTests = (workflowFiles: string[]) => { for (const filePath of workflowFiles) { const description = filePath.replace('.json', ''); const workflowData = readJsonFileSync(filePath); + const testDir = path.join(baseDir, path.dirname(filePath)); + workflowData.nodes.forEach((node) => { + if (node.parameters) { + node.parameters = JSON.parse( + JSON.stringify(node.parameters).replace(/"C:\\\\Test\\\\(.*)"/, `"${testDir}/$1"`), + ); + } + }); if (workflowData.pinData === undefined) { throw new Error('Workflow data does not contain pinData'); } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index c71e350294..d60d6ab6a0 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -683,6 +683,7 @@ export interface BinaryHelperFunctions { setBinaryDataBuffer(data: IBinaryData, binaryData: Buffer): Promise; copyBinaryFile(): Promise; binaryToBuffer(body: Buffer | Readable): Promise; + getBinaryPath(binaryDataId: string): string; getBinaryStream(binaryDataId: string, chunkSize?: number): Readable; getBinaryMetadata(binaryDataId: string): Promise; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c732f0e8a6..b361326851 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -926,9 +926,6 @@ importers: '@testing-library/vue': specifier: ^5.8.3 version: 5.8.3(vue-template-compiler@2.7.14)(vue@2.7.14) - '@types/canvas-confetti': - specifier: ^1.6.0 - version: 1.6.0 '@types/dateformat': specifier: ^3.0.0 version: 3.0.1 @@ -1131,9 +1128,9 @@ importers: otpauth: specifier: ^9.1.1 version: 9.1.1 - pdf-parse: - specifier: ^1.1.1 - version: 1.1.1 + pdfjs-dist: + specifier: ^2.16.105 + version: 2.16.105 pg: specifier: ^8.3.0 version: 8.8.0 @@ -1246,9 +1243,6 @@ importers: '@types/nodemailer': specifier: ^6.4.0 version: 6.4.6 - '@types/pdf-parse': - specifier: ^1.1.1 - version: 1.1.1 '@types/promise-ftp': specifier: ^1.3.4 version: 1.3.4 @@ -7016,10 +7010,6 @@ packages: '@types/connect': 3.4.35 '@types/node': 18.16.16 - /@types/canvas-confetti@1.6.0: - resolution: {integrity: sha512-Yq6rIccwcco0TLD5SMUrIM7Fk7Fe/C0jmNRxJJCLtAF6gebDkPuUjK5EHedxecm69Pi/aA+It39Ux4OHmFhjRw==} - dev: true - /@types/caseless@0.12.2: resolution: {integrity: sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==} dev: true @@ -7443,10 +7433,6 @@ packages: '@types/express': 4.17.14 dev: true - /@types/pdf-parse@1.1.1: - resolution: {integrity: sha512-lDBKAslCwvfK2uvS1Uk+UCpGvw+JRy5vnBFANPKFSY92n/iEnunXi0KVBjPJXhsM4jtdcPnS7tuZ0zjA9x6piQ==} - dev: true - /@types/prettier@2.7.1: resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} dev: true @@ -11470,6 +11456,11 @@ packages: dependencies: domelementtype: 2.3.0 + /dommatrix@1.0.3: + resolution: {integrity: sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==} + deprecated: dommatrix is no longer maintained. Please use @thednp/dommatrix. + dev: false + /domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} dependencies: @@ -17046,10 +17037,6 @@ packages: minimatch: 3.1.2 dev: true - /node-ensure@0.0.0: - resolution: {integrity: sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==} - dev: false - /node-fetch-native@1.0.1: resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==} dev: true @@ -17909,14 +17896,16 @@ packages: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} dev: false - /pdf-parse@1.1.1: - resolution: {integrity: sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==} - engines: {node: '>=6.8.1'} + /pdfjs-dist@2.16.105: + resolution: {integrity: sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==} + peerDependencies: + worker-loader: ^3.0.8 + peerDependenciesMeta: + worker-loader: + optional: true dependencies: - debug: 3.2.7(supports-color@8.1.1) - node-ensure: 0.0.0 - transitivePeerDependencies: - - supports-color + dommatrix: 1.0.3 + web-streams-polyfill: 3.2.1 dev: false /peek-readable@4.1.0: @@ -22296,6 +22285,11 @@ packages: graceful-fs: 4.2.10 dev: true + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}