226 lines
6.9 KiB
TypeScript
226 lines
6.9 KiB
TypeScript
const DOCUMENT_TEMPLATE_IDS = {
|
|
Certification: '1V0uMuM80BGpjpdt1AmuzlU97tDI_u-y2rOfdl4tkqmc',
|
|
};
|
|
|
|
const OUTPUT_FOLDER_ID = '1ROyJXk-QANTHM6Jiw0ne3EQiR1f2UsLr';
|
|
|
|
declare namespace GoogleAppsScript {
|
|
namespace Spreadsheet {
|
|
interface RichTextValue {
|
|
getLinkUrl(): string | null;
|
|
getLinkUrl(startOffset: number, endOffset: number): string | null;
|
|
}
|
|
}
|
|
}
|
|
|
|
type DocSection =
|
|
| GoogleAppsScript.Document.Body
|
|
| GoogleAppsScript.Document.HeaderSection
|
|
| GoogleAppsScript.Document.FooterSection;
|
|
|
|
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')
|
|
// .addItem('CAUTION! Write list of all files to current sheet', 'dumpFiles')
|
|
.addToUi();
|
|
}
|
|
|
|
function findAllFiles(path: string, folder: GoogleAppsScript.Drive.Folder) {
|
|
const out: string[][] = [];
|
|
|
|
const files = folder.getFiles();
|
|
while (files.hasNext()) {
|
|
const file = files.next();
|
|
if (!file.isTrashed()) {
|
|
out.push([
|
|
path,
|
|
file.getId(),
|
|
file.getName(),
|
|
`=HYPERLINK("${file.getUrl()}", "${file.getName()}")`,
|
|
]);
|
|
}
|
|
}
|
|
|
|
const subfolders = folder.getFolders();
|
|
while (subfolders.hasNext()) {
|
|
const subfolder = subfolders.next();
|
|
out.push(...findAllFiles(path + '/' + subfolder.getName(), subfolder));
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
function dumpFiles() {
|
|
const root = DriveApp.getFolderById('1mWOQDmQjQsi8the09VXpnKnvFUzj4wiv');
|
|
|
|
const out = findAllFiles('', root);
|
|
|
|
const active_sheet = SpreadsheetApp.getActive();
|
|
const range = active_sheet.getRange(`R1C1:R${out.length}C${out[0].length}`);
|
|
range.setValues(out);
|
|
}
|
|
|
|
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]: GoogleAppsScript.Spreadsheet.RichTextValue } {
|
|
// TODO: could be more efficient
|
|
const range = sheet.getDataRange();
|
|
const headers = range.getValues()[0];
|
|
const row_data = range.getRichTextValues()[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);
|
|
|
|
if (!(row['Type'].getText() in DOCUMENT_TEMPLATE_IDS))
|
|
throw new Error(`${row['Type']} is not a valid type of document!`);
|
|
|
|
const template_doc = DocumentApp.openById(
|
|
DOCUMENT_TEMPLATE_IDS[
|
|
row['Type'].getText() as keyof typeof DOCUMENT_TEMPLATE_IDS
|
|
]
|
|
);
|
|
|
|
const link = row['Document'].getLinkUrl();
|
|
if (link === null) {
|
|
throw new Error(`Link missing for ${row['Document'].getText()}`);
|
|
}
|
|
// TODO: should probably handle this better
|
|
if (!link.includes('document')) return;
|
|
|
|
const source_file = DriveApp.getFileById(DocumentApp.openByUrl(link).getId());
|
|
const out_folder = DriveApp.getFolderById(OUTPUT_FOLDER_ID);
|
|
|
|
const out_name = row['Document'] + '_' + 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]) => {
|
|
let replacement = data.getText();
|
|
out_doc.getBody().replaceText(`{{${header}}}`, replacement);
|
|
out_doc.getHeader().replaceText(`{{${header}}}`, replacement);
|
|
out_doc.getFooter().replaceText(`{{${header}}}`, replacement);
|
|
});
|
|
|
|
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);
|
|
}
|
|
}
|