@couleetech/n8n-nodes-pandoc
Version:
n8n node for document conversion using Pandoc
283 lines (282 loc) • 11.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PandocConvert = void 0;
const node_pandoc_1 = __importDefault(require("node-pandoc"));
const util_1 = require("util");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const os_1 = require("os");
const uuid_1 = require("uuid");
const mime_types_1 = __importDefault(require("mime-types"));
const pandocAsync = (0, util_1.promisify)(node_pandoc_1.default);
const NAMESPACE_UUID = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
class PandocConvert {
constructor() {
this.description = {
displayName: 'Pandoc Convert',
name: 'pandocConvert',
icon: 'file:pandoc.svg',
group: ['transform'],
version: 1,
description: 'Convert documents between different formats using Pandoc',
defaults: {
name: 'Pandoc Convert',
},
inputs: ['main'],
outputs: [
{
type: 'main',
displayName: 'Converted Document',
required: true,
},
{
type: 'main',
displayName: 'Extracted Images',
required: false,
},
],
properties: [
{
displayName: 'Binary Property',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
description: 'Name of the binary property that contains the file to convert',
},
{
displayName: 'From Format',
name: 'fromFormat',
type: 'options',
options: [
{
name: 'Markdown',
value: 'markdown'
},
{
name: 'HTML',
value: 'html'
},
{
name: 'Microsoft Word',
value: 'docx'
},
{
name: 'LaTeX',
value: 'latex'
},
{
name: 'Plain Text',
value: 'plain'
}
],
default: 'markdown',
description: 'Input format of the document',
},
{
displayName: 'To Format',
name: 'toFormat',
type: 'options',
options: [
{
name: 'Markdown',
value: 'markdown'
},
{
name: 'HTML',
value: 'html'
},
{
name: 'Microsoft Word',
value: 'docx'
},
{
name: 'PDF',
value: 'pdf'
},
{
name: 'LaTeX',
value: 'latex'
},
{
name: 'Plain Text',
value: 'plain'
}
],
default: 'html',
description: 'Output format for the document',
},
{
displayName: 'Additional Options',
name: 'options',
type: 'string',
default: '',
description: 'Additional pandoc options (e.g., --standalone --toc)',
required: false,
}
],
};
}
static getMimeType(format) {
const mimeTypes = {
'pdf': 'application/pdf',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'html': 'text/html',
'markdown': 'text/markdown',
'latex': 'application/x-latex',
'plain': 'text/plain',
};
return mimeTypes[format] || 'application/octet-stream';
}
static getFileName(originalName, newFormat) {
const baseName = originalName.split('.').slice(0, -1).join('.');
return `${baseName}.${PandocConvert.getFileExtension(newFormat)}`;
}
static getFileExtension(format) {
const extensions = {
'pdf': 'pdf',
'docx': 'docx',
'html': 'html',
'markdown': 'md',
'latex': 'tex',
'plain': 'txt',
};
return extensions[format] || format;
}
async execute() {
var _a;
const items = this.getInputData();
const returnData = [];
const imageData = [];
const cleanupFiles = async (paths) => {
await Promise.all(paths.map(async (path) => {
try {
const stat = await Promise.resolve().then(() => __importStar(require('fs/promises'))).then(fs => fs.stat(path));
if (stat.isDirectory()) {
await (0, promises_1.rmdir)(path, { recursive: true });
}
else {
await (0, promises_1.unlink)(path);
}
}
catch (error) {
}
}));
};
for (let i = 0; i < items.length; i++) {
const tempPaths = [];
try {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
const fromFormat = this.getNodeParameter('fromFormat', i);
const toFormat = this.getNodeParameter('toFormat', i);
const options = this.getNodeParameter('options', i, '');
const binaryData = (_a = items[i].binary) === null || _a === void 0 ? void 0 : _a[binaryPropertyName];
if (!binaryData) {
throw new Error(`No binary data found in property "${binaryPropertyName}"`);
}
const tempId = (0, uuid_1.v5)(binaryData.fileName || 'unnamed', NAMESPACE_UUID);
const tempDir = (0, os_1.tmpdir)();
const inputPath = (0, path_1.join)(tempDir, `pandoc_input_${tempId}`);
const outputPath = (0, path_1.join)(tempDir, `pandoc_output_${tempId}`);
const mediaDir = (0, path_1.join)(tempDir, `media_${tempId}`);
tempPaths.push(inputPath, outputPath, mediaDir);
await (0, promises_1.mkdir)(mediaDir, { recursive: true });
const buffer = Buffer.from(binaryData.data, 'base64');
await (0, promises_1.writeFile)(inputPath, buffer);
const args = [
'-f', fromFormat,
'-t', toFormat,
'-o', outputPath,
'--extract-media', mediaDir,
...options.split(' ').filter(Boolean)
];
await pandocAsync(inputPath, args);
const outputContent = await (0, promises_1.readFile)(outputPath);
const newBinaryData = {
[binaryPropertyName]: {
data: outputContent.toString('base64'),
mimeType: PandocConvert.getMimeType(toFormat),
fileName: PandocConvert.getFileName(binaryData.fileName || 'document', toFormat),
}
};
returnData.push({
json: items[i].json,
binary: newBinaryData,
});
try {
const mediaFiles = await (0, promises_1.readdir)(mediaDir);
for (const file of mediaFiles) {
const filePath = (0, path_1.join)(mediaDir, file);
const fileContent = await (0, promises_1.readFile)(filePath);
const mimeType = mime_types_1.default.lookup(file) || 'application/octet-stream';
const imageId = (0, uuid_1.v5)(`${binaryData.fileName}-${file}`, NAMESPACE_UUID);
const fileExt = file.split('.').pop() || '';
const deterministicImageName = `${imageId}.${fileExt}`;
imageData.push({
json: {
sourceDocument: binaryData.fileName,
originalImageName: file,
imageName: deterministicImageName,
},
binary: {
image: {
data: fileContent.toString('base64'),
mimeType,
fileName: deterministicImageName,
}
}
});
}
}
catch (error) {
}
}
catch (error) {
const pandocError = error;
if (this.continueOnFail()) {
returnData.push({
json: {
error: pandocError.message,
code: pandocError.code,
stdout: pandocError.stdout,
stderr: pandocError.stderr,
},
binary: {},
});
continue;
}
throw error;
}
finally {
await cleanupFiles(tempPaths);
}
}
return [returnData, imageData];
}
}
exports.PandocConvert = PandocConvert;