diff --git a/index.js b/index.js index da0f359..15126e2 100644 --- a/index.js +++ b/index.js @@ -190,6 +190,54 @@ class PdfTk { .replace(/\)/g, '\\)'); } + /** + * Converts a string to a buffer and appends it to an array of buffers. + * @static + * @public + * @param {Array} buffers - an array of buffers to which the result will be concatenated + * @param {String} str - String to buffer. + * @param {String} encoding - encodding + * @returns {Number} - size of the buffer + */ + static stringToBuffers(buffers, str, encoding = 'utf8') { + const buffer = PdfTk.stringToBuffer(str, encoding); + buffers.push(buffer); + return buffer.length; + } + + /** + * Returns a buffer from a JSON object representing a form field. + * @static + * @public + * @param {String} name - string representing a (partial) field name in the fdf. + * @param {Object} value - JSON object representing the value to be added to the fdf. + * @returns {Buffer} Fdf data as a buffer. + */ + static fdfFieldToBuffer(name, value) { + const buffers = []; + let len = 0; + if (value !== null && value !== undefined) { + len += PdfTk.stringToBuffers(buffers, '<<\n/T ('); + len += PdfTk.stringToBuffers(buffers, PdfTk.sanitizeForFdf(name.toString()), 'binary'); + len += PdfTk.stringToBuffers(buffers, ')\n'); + if (typeof value === 'object') { + len += PdfTk.stringToBuffers(buffers, '/Kids [\n'); + for (const prop in value) { + const buf = PdfTk.fdfFieldToBuffer(prop, value[prop]); + len += buf.length; + buffers.push(buf); + } + len += PdfTk.stringToBuffers(buffers, ']\n'); + } else { + len += PdfTk.stringToBuffers(buffers, '/V ('); + len += PdfTk.stringToBuffers(buffers, PdfTk.sanitizeForFdf(value.toString()), 'binary'); + len += PdfTk.stringToBuffers(buffers, ')\n'); + } + len += PdfTk.stringToBuffers(buffers, '>>\n'); + } + return Buffer.concat(buffers, len); + } + /** * Creates fdf file from JSON input. * Converts input values to binary buffer, which seems to allow PdfTk to render utf-8 characters. @@ -199,63 +247,39 @@ class PdfTk { * @returns {Buffer} Fdf data as a buffer. */ static generateFdfFromJSON(data) { - - const header = PdfTk.stringToBuffer(` - %FDF-1.2\n - ${String.fromCharCode(226) + String.fromCharCode(227) + String.fromCharCode(207) + String.fromCharCode(211)}\n - 1 0 obj\n - <<\n - /FDF\n - <<\n - /Fields [\n - `); - - let body = PdfTk.stringToBuffer(''); + const buffers = []; + let len = 0; + len += PdfTk.stringToBuffers(buffers, `%FDF-1.2 +%${String.fromCharCode(226) + String.fromCharCode(227) + String.fromCharCode(207) + String.fromCharCode(211)} +1 0 obj +<< +/FDF +<< +/Fields [ +`, 'binary'); for (const prop in data) { /* istanbul ignore else */ - if (data.hasOwnProperty(prop) && data[prop] !== null && data[prop] !== undefined) { - body = Buffer.concat([ - body, - PdfTk.stringToBuffer('<<\n/T ('), - ]); - body = Buffer.concat([ - body, - PdfTk.stringToBuffer(PdfTk.sanitizeForFdf(prop.toString()), 'binary'), - ]); - body = Buffer.concat([ - body, - PdfTk.stringToBuffer(')\n/V('), - ]); - body = Buffer.concat([ - body, - PdfTk.stringToBuffer(PdfTk.sanitizeForFdf(data[prop].toString()), 'binary'), - ]); - body = Buffer.concat([ - body, - PdfTk.stringToBuffer(')\n>>\n'), - ]); + if (data.hasOwnProperty(prop)) { + const buf = PdfTk.fdfFieldToBuffer(prop, data[prop]); + len += buf.length; + buffers.push(buf); } } - const footer = PdfTk.stringToBuffer(` - ]\n - >>\n - >>\n - endobj \n - trailer\n - \n - <<\n - /Root 1 0 R\n - >>\n - %%EOF\n - `); - - return Buffer.concat([ - header, - body, - footer, - ]); + len += PdfTk.stringToBuffers(buffers, `] +>> +>> +endobj +trailer + +<< +/Root 1 0 R +>> +%%EOF +`, 'binary'); + + return Buffer.concat(buffers, len); } @@ -341,9 +365,19 @@ class PdfTk { const result = []; child.stderr.on('data', data => { - if (!this._ignoreWarnings && data.toString().toLowerCase().includes('error')) { - this._cleanUpTempFiles(); - return reject(data.toString('utf8')); + if (!this._ignoreWarnings) { + let str = data.toString(); + if (str.toLowerCase().includes('error')) { + this._cleanUpTempFiles(); + try { + str = data.toString('utf8'); + } catch (e) { + // toString('utf8') can throw when toString() (above) did not... + // if it does, ignore this secondary error and + // reject using the string we already have. + } + return reject(str); + } } }); diff --git a/pretest.js b/pretest.js index 3ec1d65..5b1c730 100644 --- a/pretest.js +++ b/pretest.js @@ -21,11 +21,13 @@ function sequentialPromise(operations) { const commands = [ 'test/files/form.pdf fill_form test/files/form.fdf output test/files/filledform.temp.pdf', + 'test/files/form2.pdf fill_form test/files/form2.fdf output test/files/filledform2.temp.pdf', 'test/files/form.pdf fill_form test/files/formwithnumber.fdf output test/files/filledformwithnumber.temp.pdf', 'test/files/form.pdf fill_form test/files/form.fdf output test/files/filledformflat.temp.pdf flatten', 'test/files/form.pdf fill_form test/files/form.blank.fdf output test/files/filledformempty.temp.pdf flatten', 'test/files/form.pdf fill_form test/files/form.escape.fdf output test/files/filledformescape.temp.pdf flatten', 'test/files/form.pdf generate_fdf output test/files/form.temp.fdf', + 'test/files/form2.pdf generate_fdf output test/files/form2.temp.fdf', 'test/files/form.pdf stamp test/files/logo.pdf output test/files/stamp.temp.pdf', 'test/files/form.pdf multistamp test/files/logo.pdf output test/files/multistamp.temp.pdf', 'A=test/files/document1.pdf B=test/files/document2.pdf cat A B output test/files/documentcatwithdate.temp.pdf keep_final_id', 'test/files/documentcatwithdate.temp.pdf update_info_utf8 test/files/documentcat.info output test/files/documentcat.temp.pdf', @@ -33,6 +35,7 @@ const commands = [ 'test/files/form.pdf multibackground test/files/logo.pdf output test/files/multibackground.temp.pdf', 'test/files/form.pdf dump_data_fields output test/files/form.fields.temp.info', 'test/files/form.pdf dump_data_fields_utf8 output test/files/form.fields.utf8.temp.info', + 'test/files/form2.pdf dump_data_fields_utf8 output test/files/form2.fields.utf8.temp.info', ].map(i => pdftk.bind(this, i)); diff --git a/test/dumpDataFieldsUtf8.spec.js b/test/dumpDataFieldsUtf8.spec.js index 9edc191..ca79a6b 100644 --- a/test/dumpDataFieldsUtf8.spec.js +++ b/test/dumpDataFieldsUtf8.spec.js @@ -22,6 +22,19 @@ describe('dumpDataFieldsUtf8', function () { .catch(err => expect(err).to.be.null); }); + it('should output hierarchical data fields to file with file path input', function () { + + const testFile = fs.readFileSync(path.join(__dirname, './files/form2.fields.utf8.temp.info')); + const input = path.join(__dirname, './files/form2.pdf'); + + return pdftk + .input(input) + .dumpDataFieldsUtf8() + .output() + .then(buffer => expect(buffer.equals(testFile)).to.be.true) + .catch(err => expect(err).to.be.null); + }); + it('should output data fields to file with buffer input', function () { const testFile = fs.readFileSync(path.join(__dirname, './files/form.fields.utf8.temp.info')); diff --git a/test/files/form2.fdf b/test/files/form2.fdf new file mode 100644 index 0000000..96d3489 --- /dev/null +++ b/test/files/form2.fdf @@ -0,0 +1,35 @@ +%FDF-1.2 +%âãÏÓ +1 0 obj +<< +/FDF +<< +/Fields [ +<< +/T (topmostSubform[0]) +/Kids [ +<< +/T (Page1[0]) +/Kids [ +<< +/T (f1_1[0]) +/V (John Doe) +>> +<< +/T (f1_2[0]) +/V (123-12-3123) +>> +] +>> +] +>> +] +>> +>> +endobj +trailer + +<< +/Root 1 0 R +>> +%%EOF diff --git a/test/files/form2.pdf b/test/files/form2.pdf new file mode 100644 index 0000000..04f6c79 Binary files /dev/null and b/test/files/form2.pdf differ diff --git a/test/fillForm.spec.js b/test/fillForm.spec.js index 80e76ff..082188b 100644 --- a/test/fillForm.spec.js +++ b/test/fillForm.spec.js @@ -25,6 +25,29 @@ describe('fillForm', function () { .catch(err => expect(err).to.be.null); }); + it('should fill a form featuring partially qualified field names (hierarchical/grouped fields)', function () { + + const testFile = fs.readFileSync(path.join(__dirname, './files/filledform2.temp.pdf')); + const input = path.join(__dirname, './files/form2.pdf'); + + return pdftk + .input(input) + .fillForm({ + 'topmostSubform[0]': { + 'Page1[0]': { + 'f1_1[0]': 'John Doe', + 'f1_2[0]': '123-12-3123', + }, + }, + }) + .output() + .then(buffer => expect(buffer.equals(testFile)).to.be.true) + .catch(err => { + if (err) console.error(err); + return expect(err).to.be.null; + }); + }); + it('should fill a form without flattening it with a number input', function () { const testFile = fs.readFileSync(path.join(__dirname, './files/filledformwithnumber.temp.pdf')); diff --git a/test/generateFdf.spec.js b/test/generateFdf.spec.js index a50ec52..6167921 100644 --- a/test/generateFdf.spec.js +++ b/test/generateFdf.spec.js @@ -22,6 +22,19 @@ describe('generateFdf', function () { .catch(err => expect(err).to.be.null); }); + it('should generate a hierarchical fdf from a pdf containing partially qualified field names', function () { + + const testFile = fs.readFileSync(path.join(__dirname, './files/form2.temp.fdf')); + const input = path.join(__dirname, './files/form2.pdf'); + + return pdftk + .input(input) + .generateFdf() + .output() + .then(buffer => expect(buffer.equals(testFile)).to.be.true) + .catch(err => expect(err).to.be.null); + }); + it('should generate an fdf file from a pdf with a buffer input', function () { const testFile = fs.readFileSync(path.join(__dirname, './files/form.temp.fdf'));