Use template as function (to merge multiple PDFs into one PDF)

Hi everyone,

@bakman2 helped me using a npm module in a function node, which is now working (Useage of contrib-npm)

I now have to dynamically change the files which need to be merged. Therefore I'm using some change nodes and a template to generate the code:

[
    {
        "id": "45c5e484f9cc2a79",
        "type": "function",
        "z": "4b453fb457938d95",
        "name": "merge",
        "func": "return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "pdfMergerJs",
                "module": "pdf-merger-js"
            },
            {
                "var": "pdfLib",
                "module": "pdf-lib"
            }
        ],
        "x": 1110,
        "y": 1000,
        "wires": [
            [
                "8539b0d2158b0dfa"
            ]
        ]
    },
    {
        "id": "8539b0d2158b0dfa",
        "type": "debug",
        "z": "4b453fb457938d95",
        "name": "debug 41",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1140,
        "y": 1080,
        "wires": []
    },
    {
        "id": "033b5c54563992b4",
        "type": "template",
        "z": "4b453fb457938d95",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "const merger = new pdfMergerJs();\n\n(async () => {\n{{files}};\n\n  await merger.save('/media/qsl/merged.pdf'); //save under given name and reset the internal document\n})();\nreturn msg;",
        "output": "str",
        "x": 840,
        "y": 980,
        "wires": [
            [
                "45c5e484f9cc2a79",
                "8539b0d2158b0dfa"
            ]
        ]
    },
    {
        "id": "0ee65a7f2c1c0d23",
        "type": "join",
        "z": "4b453fb457938d95",
        "name": "",
        "mode": "custom",
        "build": "string",
        "property": "files",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "accumulate": false,
        "timeout": "5",
        "count": "",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "num",
        "reduceFixup": "",
        "x": 710,
        "y": 820,
        "wires": [
            [
                "033b5c54563992b4"
            ]
        ]
    },
    {
        "id": "c1cc0d67422f0205",
        "type": "change",
        "z": "4b453fb457938d95",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "\"file_\" & $.payload",
                "tot": "jsonata"
            },
            {
                "t": "set",
                "p": "files",
                "pt": "msg",
                "to": "\"  await merger.add('\" & $.payload & \"');\"",
                "tot": "jsonata"
            },
            {
                "t": "delete",
                "p": "payload",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 500,
        "y": 820,
        "wires": [
            [
                "0ee65a7f2c1c0d23"
            ]
        ]
    },
    {
        "id": "de4390fe21e4fcc3",
        "type": "fs-file-lister",
        "z": "4b453fb457938d95",
        "name": "",
        "start": "/media/qsl/",
        "pattern": "*.*",
        "folders": "*",
        "hidden": true,
        "lstype": "files",
        "path": true,
        "single": false,
        "depth": 0,
        "stat": false,
        "showWarnings": true,
        "x": 280,
        "y": 820,
        "wires": [
            [
                "c1cc0d67422f0205"
            ]
        ]
    },
    {
        "id": "a1f0df561ff43be4",
        "type": "inject",
        "z": "4b453fb457938d95",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 140,
        "y": 820,
        "wires": [
            [
                "de4390fe21e4fcc3"
            ]
        ]
    }
]

However, this is not working.

My first question is, if it's even possible to work as I did it (putting out the code generated in the template node as a string and then just doing "return msg;" in the function node, where the required npm nodes are set).
The other question is, why do I get the following output from the template (all signs which are loaded from a mustache variable are not shown correct):

const merger = new pdfMergerJs();

(async () => {
  await merger.add('/media/qsl/qsl1.pdf');
  await merger.add('/media/qsl/qsl2.pdf');;

  await merger.save('/media/qsl/merged.pdf'); //save under given name and reset the internal document
})();
return msg

Is this an encoding issue or do I have to change the template?

Thanks

EDIT: format of the template output is correct after using three { & } around the mustache variable instead of just two.
But the function is still not working.

EDIT2: changing the function to var payload = msg.payload; return msg; creates an output from the function node, but it's not generating the merged pdf.

Anyone any idea?

I'd also directly write a function which uses the payload in await merger.add but I'm not capable of doing so.

I'd really appreciate someones input on that.

Thanks!

You cannot use a template node to build javascript then expect the function node to execute it.

Here is a working routine...

I have fully commented the code in the function node to help you understand it.

const merger = new pdfMergerJs()

//the array of files from fs-ops
const files = msg.files

//check there are 2 or more
if (!files || !files.length || files.length < 2) {
    node.warn("Not enough files to merge. there should be 2 or more PDFs");
    return
}

// loop the file names from fs-ops-directory
for (let index = 0; index < files.length; index++) {
    const file = files[index] // this file name
    const fullPath = path.join(msg.path, file) // make full path
    await merger.add(fullPath) // add the full path to pdf
}

// join and return a buffer
msg.payload = await merger.saveAsBuffer() 

// set msg.filename for write node
msg.filename = msg.result // msg.result is configured in the inject node

// pass the msg to next node
node.send(msg);

The flow...

  • First, install node-red-contrib-fs-ops
  • Copy this flow
  • Import it using CTRL-I)
  • Set your file paths in the inject node
  • The output path must exist
[{"id":"45c5e484f9cc2a79","type":"function","z":"49f61d916c8f6022","name":"merge","func":"const merger = new pdfMergerJs()\n\n//the array of files from fs-ops\nconst files = msg.files\n\n//check there are 2 or more\nif (!files || !files.length || files.length < 2) {\n    node.warn(\"Not enough files to merge. there should be 2 or more PDFs\");\n    return\n}\n\n// loop the file names from fs-ops-directory\nfor (let index = 0; index < files.length; index++) {\n    const file = files[index] // this file name\n    const fullPath = path.join(msg.path, file) // make full path\n    await merger.add(fullPath) // add the full path to pdf\n}\n\n// join and return a buffer\nmsg.payload = await merger.saveAsBuffer() \n\n// set msg.filename for write node\nmsg.filename = msg.output // msg.result is configured in the inject node\n\n// pass the msg to next node\nnode.send(msg);\n\n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"pdfMergerJs","module":"pdf-merger-js"},{"var":"pdfLib","module":"pdf-lib"},{"var":"path","module":"path"}],"x":590,"y":1220,"wires":[["b4d921e6c85b4e4b","3697a5b1be9bcb2a"]]},{"id":"a1f0df561ff43be4","type":"inject","z":"49f61d916c8f6022","name":"set path","props":[{"p":"path","v":"c:\\temp\\pdfs","vt":"str"},{"p":"output","v":"c:\\temp\\pdfs\\merged\\result.pdf","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":250,"y":1220,"wires":[["75bfb77145ed2fc2"]]},{"id":"75bfb77145ed2fc2","type":"fs-ops-dir","z":"49f61d916c8f6022","name":"","path":"path","pathType":"msg","filter":"*.pdf","filterType":"str","dir":"files","dirType":"msg","x":420,"y":1220,"wires":[["c65a17212c52cf52","45c5e484f9cc2a79"]]},{"id":"8539b0d2158b0dfa","type":"debug","z":"49f61d916c8f6022","name":"debug 41","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":920,"y":1220,"wires":[]},{"id":"c65a17212c52cf52","type":"debug","z":"49f61d916c8f6022","name":"debug 42","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"files","targetType":"msg","statusVal":"","statusType":"auto","x":470,"y":1300,"wires":[]},{"id":"b4d921e6c85b4e4b","type":"file","z":"49f61d916c8f6022","name":"","filename":"filename","filenameType":"msg","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":740,"y":1220,"wires":[["8539b0d2158b0dfa"]]},{"id":"3697a5b1be9bcb2a","type":"debug","z":"49f61d916c8f6022","name":"debug 43","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":1300,"wires":[]},{"id":"fc51cb0be6142e56","type":"comment","z":"49f61d916c8f6022","name":"Set your paths here 👇","info":"","x":240,"y":1180,"wires":[],"icon":"font-awesome/fa-long-arrow-down"}]

The result...

image
image
image

1 Like

Dear @Steve-Mcl,

thanks a lot for your support. Not only on this post, but in general in this forum. I've read multiple solutions from you in this forum and they were always extremely helpful.

Your function and code is working as expected.

I will present the complete flow in an amateur radio reddit board as it is about printing QSL cards from a logbook and will post a link to it.

Again: thank you very much

2 Likes

Here is the link to my post in a amateur radio board on reddit:

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.