function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu('CMS Document Generation') .addItem('Generate document for current row', 'generateForCurrentRow') .addItem('Generate document for all rows', 'generateForAllRows') .addToUi(); } type DocSection = | GoogleAppsScript.Document.Body | GoogleAppsScript.Document.HeaderSection | GoogleAppsScript.Document.FooterSection; function copyElement( source_element: GoogleAppsScript.Document.Element, dest: DocSection, index?: number ) { const element = source_element.copy(); switch (element.getType()) { case DocumentApp.ElementType.PARAGRAPH: if (index !== undefined) dest.insertParagraph(index, element.asParagraph()); else dest.appendParagraph(element.asParagraph()); break; case DocumentApp.ElementType.TABLE: if (index !== undefined) dest.insertTable(index, element.asTable()); else dest.appendTable(element.asTable()); break; case DocumentApp.ElementType.LIST_ITEM: if (index !== undefined) dest.insertListItem(index, element.asListItem()); else dest.appendListItem(element.asListItem()); break; case DocumentApp.ElementType.INLINE_IMAGE: if (index !== undefined) dest.insertImage(index, element.asInlineImage()); else dest.appendImage(element.asInlineImage()); break; default: throw new Error( "According to the doc this type couldn't appear in the body: " + element.getType() ); } } function copySection(source: DocSection, dest: DocSection, index?: number) { const totalElements = source.getNumChildren(); for (let j = 0; j < totalElements; ++j) { if (index) copyElement(source.getChild(j), dest, index + j); else copyElement(source.getChild(j), dest); } } function spreadsheetRowToObject( sheet: GoogleAppsScript.Spreadsheet.Sheet, rownum: number ): { [key: string]: any } { // TODO: could be more efficient const values = sheet.getDataRange().getValues(); const headers = values[0]; const row_data = values[rownum - 1]; return headers.reduce((acc, header, index) => { acc[String(header)] = row_data[index]; return acc; }, {}); } function trashFiles(files: GoogleAppsScript.Drive.FileIterator) { while (files.hasNext()) { files.next().setTrashed(true); } } function generateForRow( spreadsheet: GoogleAppsScript.Spreadsheet.Spreadsheet, row_num: number ) { const row = spreadsheetRowToObject(spreadsheet.getActiveSheet(), row_num); const template_doc = DocumentApp.openById(row['Template ID']); const source_file = DriveApp.getFileById(row['Document ID']); const out_folder = DriveApp.getFolderById( '1ROyJXk-QANTHM6Jiw0ne3EQiR1f2UsLr' ); const out_name = row['Document Name'] + '_' + row['Version']; // Delete old files with the same name trashFiles(out_folder.getFilesByName(out_name)); trashFiles(out_folder.getFilesByName(out_name + '.pdf')); // Duplicate source document and open const out_file = source_file.makeCopy(out_name, out_folder); const out_doc = DocumentApp.openById(out_file.getId()); // Copy header if (!out_doc.getHeader()) out_doc.addHeader(); copySection(template_doc.getHeader(), out_doc.getHeader().clear()); // Copy footer if (!out_doc.getFooter()) out_doc.addFooter(); copySection(template_doc.getFooter(), out_doc.getFooter().clear()); const insert_point = template_doc .getBody() .findText('{{body}}') ?.getElement(); if (!insert_point) { throw new Error("Could not find insert point '{{body}}'"); } // find the parent element that is a direct descendant of the body let parent = insert_point; while (parent.getParent().getType() != DocumentApp.ElementType.BODY_SECTION) { parent = parent.getParent(); } const index = template_doc.getBody().getChildIndex(parent); // Copy body contents above and below {{body}} tag for (let j = 0; j < template_doc.getBody().getNumChildren(); j++) { if (j < index) copyElement(template_doc.getBody().getChild(j), out_doc.getBody(), j); // don't copy {{body}} tag else if (j == index) continue; else copyElement(template_doc.getBody().getChild(j), out_doc.getBody()); } // do text replacement Object.entries(row).forEach(([header, data]) => { out_doc.getBody().replaceText(`{{${header}}}`, String(data)); out_doc.getHeader().replaceText(`{{${header}}}`, String(data)); out_doc.getFooter().replaceText(`{{${header}}}`, String(data)); }); out_doc.saveAndClose(); // create PDF file out_folder.createFile(out_file.getAs('application/pdf')); } function generateForCurrentRow() { const spreadsheet = SpreadsheetApp.getActive(); const cell = spreadsheet.getCurrentCell(); if (!cell) throw new Error('No Cell selected for operation on row'); generateForRow(spreadsheet, cell.getRow()); } function generateForAllRows() { const spreadsheet = SpreadsheetApp.getActive(); const row_count = spreadsheet.getActiveSheet().getDataRange().getNumRows(); for (let row_num = 2; row_num < row_count; row_num++) { generateForRow(spreadsheet, row_num); } }