I am trying to create a script that will take a Google Doc template document, make a copy, replace certain text with information from a row on my spreadsheet, append another page, replace the text with information from the next row on the spreadsheet, append another page, etc.
Here is what I have so far:
// Global variables
var templateDocID = ScriptProperties.getProperty("backRxRequestDocID");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var activeSheetName = sheet.getName();
var user = Session.getUser().getEmail();
function requestGen3() {
var physName = ["doc john", "doc evan", "doc jane"];
var physAddr1 = ["fake st.", "faker st.", "fakest st."];
var physAddr2 = ["ste 100", "", "ste 209"];
var physCity = ["SLC", "Provo", "Orem"];
var physState = ["UT", "AZ", "NV"];
var physZip = ["84049", "84044", "84601"];
var physPhone = ["8015555555", "7206666666", "4803333333"];
var ptName = ["ed", "sue", "izzy"];
var ptDOB = ["12/10/1979", "1/1/2001", "45/94/4561"];
// Get document template, copy it as a new temp doc, and save the Doc’s id
var docID = DocsList.getFileById(templateDocID).makeCopy().getId();
var doc = DocumentApp.openById(docID);
var body = doc.getActiveSection();
var pars = doc.getParagraphs();
var bodyCopy = body;
for (var i = 0; i < physName.length; ++i) {
// Replace place holder keys,
body.replaceText('%PHYS_NAME%', physName[i]);
body.replaceText('%PHYS_ADDR1%', physAddr1[i]);
body.replaceText('%PHYS_ADDR2%', physAddr2[i]);
body.replaceText('%PHYS_CITY%', physCity[i]);
body.replaceText('%PHYS_STATE%', physState[i]);
body.replaceText('%PHYS_ZIP%', physZip[i]);
body.replaceText('%PHYS_PHONE%', physPhone[i]);
body.replaceText('%PT_NAME%', ptName[i]);
body.replaceText('%PT_DOB%', ptDOB[i]);
doc.appendPageBreak();
for (var j = 0; j < pars.length; ++j) {
doc.appendParagraph(pars[j].copy());
}
}
// Save and close the document
doc.saveAndClose();
}
I went through the tutorial on reading from the spreadsheet but I couldn't seem to make getRowsData() and getObjects() to work properly. My script above is creating the document properly but is not inserting the second set of info into the second page and third set into the third page, etc.
You have to replace the text only after you copied the paragraphs, because if you do it after, the placeholders will be already replaced and will not be present for the next copies. You can do it like this:
//...
var pars = doc.getParagraphs();
for( var i in pars ) //loop to keep a copy of the original paragraphs
pars[i] = pars[i].copy();
for( var i = 0; i < physName.length; ++i ) {
body.replaceText('%PHYS_NAME%', physName[i]);
//do all your replaces...
if( i != physName.length-1 ) { //has next?
doc.appendPageBreak();
for( var j in pars )
doc.appendParagraph(pars[j].copy());
}
}
doc.saveAndClose();
The following code will read your spreadsheet and create a matrix called data.
var sh = SpreadsheetApp.getActive().getActiveSheet();
var lastRow = sh.getLastRow();
var lastCol = sh.getLastColumn();
var data = sh.getRange(1, 1, lastRow, lastCol).getValues();
Related
This question already has answers here:
Long processing time likely due to getValue and cell inserts
(2 answers)
Closed 8 months ago.
I have a code working fine but not optimized (I am new to Google App script).
This code is doing the following :
Get data from external URL
Filter the data
Parse data in sheets contained in a folder
Change columns header
Appen content in a specific column
function myfunction() {
var keywords = ["valuetoremove1", "valuetoremove2"]; // filter the column "C".
// Retrieve CSV data.
var csvUrl = "https://myurl";
var csvContent = UrlFetchApp.fetch(csvUrl).getContentText();
var csvData = Utilities.parseCsv(csvContent, ";");
// Retrieve Spreadsheet and put the CSV data.
var root = DriveApp.getFoldersByName("Folder1");
while (root.hasNext()) {
var folder = root.next();
var files = folder.getFiles();
while (files.hasNext()) {
var spreadsheet = SpreadsheetApp.open(files.next());
var name = spreadsheet.getName().toUpperCase();
var values = csvData.reduce((ar, r) => {
if (!keywords.some(e => r[2].toUpperCase().includes(e.toUpperCase())) && r.join("").toUpperCase().includes(name)) {
ar.push(r);
}
return ar;
}, []);
if (values.length == 0) continue;
var sheet = spreadsheet.getSheets()[0];
sheet.clearContents().getRange(2, 1, values.length, values[0].length).setValues(values);
// modify column titles
var cell = sheet.getRange(1,1);
cell.setValue("Column1");
var cell = sheet.getRange(1,2);
cell.setValue("Column2");
var cell = sheet.getRange(1,3);
cell.setValue("Column3");
var cell = sheet.getRange(1,4);
cell.setValue("Column4");
var cell = sheet.getRange(1,5);
cell.setValue("Column5");
// Modify column E
var dataRange = spreadsheet.getDataRange().getValues();
var colData = [];
for (var i = 1; i < dataRange.length; i++) {
colData.push(dataRange[i][0]);
}
for (var i = 0; i < colData.length; i++) {
// Get column E
var comments_cell = spreadsheet.getDataRange().getCell(i + 2, 5).getValue();
// Append
spreadsheet.getDataRange().getCell(i + 2, 5).setValue('<button type="button">Button</button>');
}
}
}
}
It works but it takes ages, especially the last part, the lines are being changed one by one.
Is there any way to make it much faster ?
Thanks
Try (after // Modify column E)
var dataRange = spreadsheet.getDataRange().getValues()
for (var i = 1; i < dataRange.length; i++) {
// Get column E
var comments_cell = dataRange[i][4];
dataRange[i][4] = '<button type="button">Button</button>'
}
spreadsheet.getDataRange().setValues(dataRange)
I am having the following code for appending table in Google Docs.
var sss = SpreadsheetApp.openById('id of spreadsheet');
var rawData = sss.getDataRange().getValues()
var data = []
for (var i = 0; i< rawData.length; i++){
tempData=[]
tempData=[rawData[i][1],rawData[i][2],rawData[i][3]]
data.push(tempData)
}
var someDoc = DocumentApp.openById(someId);
var body = someDoc.getBody();
body.appendTable(data).editAsText().setBold(false);
This code works fine. The problem is that there is url in rawdata[i][3]. It gets displayed in Google doc as plain text. How can I convert it into hyperlink? It would be even better if it is possible to write it as body.appendParagraph("my link").setLinkUrl("http://www.google.com"). The problem is that it is in an array, not in paragraph.
I believe your goal is as follows.
You want to put a table to Google Document by retrieving the values from Google Spreadsheet.
In your Spreadsheet, the column "D" has the hyperlinks. And, you want to set the value as the hyperlink.
In this case, how about the following modification?
Modified script:
var sss = SpreadsheetApp.openById('id of spreadsheet');
var rawData = sss.getDataRange().getValues();
var data = []
for (var i = 0; i < rawData.length; i++) {
tempData = []
tempData = [rawData[i][1], rawData[i][2], rawData[i][3]]
data.push(tempData)
}
var someDoc = DocumentApp.openById(someId);
var body = someDoc.getBody();
// I modified below script.
var table = body.appendTable(data);
for (var r = 0; r < table.getNumRows(); r++) {
var obj = table.getCell(r, 2).editAsText();
var text = obj.getText();
if (/^https?:\/\//.test(text)) obj.setLinkUrl(text);
}
table.editAsText().setBold(false);
When this script is run, a table is put using the values retrieved from Spreadsheet. And, about the column "C" of the table, the text is changed to the hyperlink.
Note:
This modified script supposes that your values of column "D" are like https://### and http://###. Please be careful about this.
If you want to give the specific text (for example, click here) with the hyperlink, please modify as follows.
From
if (/^https?:\/\//.test(text)) obj.setLinkUrl(text);
To
if (/^https?:\/\//.test(text)) obj.setText("click here").setLinkUrl(text);
References:
getCell(rowIndex, cellIndex) of Class Table
setLinkUrl(url)
You can try the following to make all URLs in a Google Doc clickable:
function urlClickable() {
var urlRegex = 'http[s]?:\/\/[^ ]+';
var doc = DocumentApp.openById('yourDocId');
var body = doc.getBody();
var urlElement = body.findText(urlRegex);
while (urlElement != null) {
var text = urlElement.getElement().asText();
var startOffset = urlElement.getStartOffset();
var endOffset = urlElement.getEndOffsetInclusive();
text.setLinkUrl(startOffset, endOffset, getUrl(text.getText()));
urlElement = body.findText(urlRegex, urlElement);
}
}
function getUrl(text) {
var startOffset = text.indexOf('http');
var endOffset = text.indexOf(' ', startOffset);
if (endOffset === -1) {
endOffset = text.length;
}
return text.substring(startOffset, endOffset);
}
Hoping someone could just point out my probably obvious mistakes.
Code searches a Data page & copies matched info to a Results page. This is fine & works quickly.
What happens is when I add an overwrite section to the script. It overwrites matched entries as "Done" on the data page, so if the Data page gets added to, the previous entries won't match again.
I have 2 variants I've tried that work but are quite slow...as in you can sit & watch each match change to "Done" once every few seconds.
Appreciate any insight.
Here's the code:
function cellMatch() {
// Sheet Import
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Raw_Data"); // Data
var sheet2 = ss.getSheetByName("Pallet_Data");// Result
var sheet3 = ss.getSheetByName("Close");// Search
// Data Import
var lr = sheet1.getLastRow();
var data = sheet1.getRange(2,1,lr-1,2).getValues();
var lc = sheet2.getLastColumn()+1;
var key = sheet3.getRange("A2").getValue();
var matched = [["Pallet "+lc+" ("+key+")"]];
//Start
for (var i=0; i<lr-1; i++) {
if (data[i][1] == key) {
var temp = [];
temp.push(data[i][0]);
matched.push(temp);
// Slow Overwrite-----------------------------------
/*
if(i>0){
var temp1 = sheet1.getRange(i,2).getValue();
var temp2 = sheet1.getRange(i+1,2).getValue();
var temp3 = sheet1.getRange(i+2,2).getValue();
// Testing1
if(temp1 == key ){
sheet1.getRange(i,2).setValue("Done");
}
if (i== lr-2){
if (temp2 == key){
sheet1.getRange(i+1,2).setValue("Done");
}
if (temp3 == key){
sheet1.getRange(i+2,2).setValue("Done");
}
}
}
*/
}
// Shorter code but even slower overwrite----------------
/*
for(var i=1; i<=lr; i++){
var temp1 = sheet1.getRange(i,2).getValue();
if(temp1 == key){
sheet1.getRange(i,2).setValue("Done");
}
}
*/
// Location Update ---------------------------------
var A7 = sheet3.getRange("A7");
A7.setValue(matched);
// Data Write -----------------------------------------
var result = sheet2.getRange(1,lc,matched.length);
result.setValues(matched);
}
Link to the Test Sheet
Solution:
The script runs slow because there are API calls in for loops. Best practice is to manipulate only the array inside the loop and do the API calls before and after the loop.
In your code it would look like this:
function cellMatch() {
// Sheet Import
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Raw_Data"); // Data
var sheet2 = ss.getSheetByName("Pallet_Data");// Result
var sheet3 = ss.getSheetByName("Close");// Search
// Data Import
var lr = sheet1.getLastRow();
var datarange = sheet1.getRange(2,1,lr-1,2);
var data = datarange.getValues();
var lc = sheet2.getLastColumn()+1;
var key = sheet3.getRange("A2").getValue();
var matched = [["Pallet "+lc+" ("+key+")"]];
//Start
for (var i=0; i<lr-1; i++) {
if (data[i][1] == key) {
var temp = [];
temp.push(data[i][0]);
matched.push(temp);
data[i][1] = "Done";
}
// Location Update ---------------------------------
var A7 = sheet3.getRange("A7");
A7.setValue(matched);
// Data Write -----------------------------------------
datarange.setValues(data);
var result = sheet2.getRange(1,lc,matched.length);
result.setValues(matched);
}
Note that the for loop does not have any getValue()/setValue() methods.
This has the same effect on your sample sheet, only way faster.
I'm trying to create a script that will create new documents from a template-document. Replace placeholders in the documents with data from the sheet based on a keyword search in a specific column. And then change the row's value in the specific column so that the row will not process when the script runs again.
I think I've got it right with the first keyword search, and the loop through the rows. But the last part to get the data to 'merge' to the placeholders I can't figure out how to. I just get the value "object Object" and other values in the document.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var lastColumn = s.getLastColumn();
function createDocFromSheet() {
var headers = getUpsertHeaders(s);//function is defined outside of this function
var statusColNum = headers.indexOf('Status')+1;
var row = getRowsData(s); //The function is defined outside this function.
for (var i=0; i<row.length; i++) {
var jobStatus = '';
if (row[i]['Status'] === '') {
//New: write the status to the correct row and column - this will be moved to the end when I get the rest right
var jobStatus = "Created";
s.getRange(i+2, statusColNum).setValue(jobStatus);
//Find the template and make a copy. Activate the body of the new file.
var templateFile = DriveApp.getFileById('1lkfmqsJMjjPujHqDqKtcDmL-5GMIxpOWTyCOaK29d2A');
var copyFile = templateFile.makeCopy()
var copyId = copyFile.getId()
var copyDoc = DocumentApp.openById(copyId)
var copyBody = copyDoc.getActiveSection()
//Find the rows Values as an object.
var rows = s.getRange(i+2,1,1,lastColumn)
var rowsValues = rows.getValues();
Logger.log(rowsValues)
//Until here I think it's okay but the last part?
//HOW TO replace the text???
for (var columnIndex = 0; columnIndex < lastColumn; columnIndex++) {
var headerValue = headerRow[columnIndex]
var rowValues = s.getActiveRange(i,columnIndex).getValues()
var activeCell = rowsValues[columnIndex]
//var activeCell = formatCell(activeCell);
Logger.log(columnIndex);
copyBody.replaceText('<<' + headerValue + '>>', activeCell)
}
Template doc : Link
Template sheet: Link
You can use the following GAS code to accomplish your goals:
var DESTINATION_FOLDER_ID = 'YOUR_DESTINATION_FOLDER_ID';
var TEMPLATE_FILE_ID = 'YOUR_TEMPLATE_FILE_ID';
function fillTemplates() {
var sheet = SpreadsheetApp.getActiveSheet();
var templateFile = DriveApp.getFileById(TEMPLATE_FILE_ID);
var values = sheet.getDataRange().getDisplayValues();
var destinationFolder = DriveApp.getFolderById(DESTINATION_FOLDER_ID);
for (var i=1; i<values.length; i++) {
var rowElements = values[i].length;
var fileStatus = values[i][rowElements-1];
if (fileStatus == 'Created') continue;
var fileName = values[i][0];
var newFile = templateFile.makeCopy(fileName, destinationFolder);
var fileToEdit = DocumentApp.openById(newFile.getId());
for (var j=1; j<rowElements-1; j++) {
var header = values[0][j];
var docBody = fileToEdit.getBody();
var patternToFind = Utilities.formatString('<<%s>>', header);
docBody.replaceText(patternToFind, values[i][j]);
}
sheet.getRange(i+1, rowElements).setValue('Created');
}
}
You only have to replace the 1st and 2nd lines as appropriate. Please do consider as well that the code will assume that the first column is the file name, and the last one the status. You can insert as many columns as you wish in between.
After some coding I ended up with this code to process everything automatic.
Again thanks to #carlesgg97.
The only thing I simply can't figure out now is how to generate the emailbody from the template with dynamic placeholders like in the document. How to generate the var patternToFind - but for the emailbody?
I've tried a for(var.... like in the document but the output doesn't replace the placeholders.
var DESTINATION_FOLDER_ID = '1inwFQPmUu1ekGGSB5OnWLc_8Ac80igK0';
var TEMPLATE_FILE_ID = '1lkfmqsJMjjPujHqDqKtcDmL-5GMIxpOWTyCOaK29d2A';
function fillTemplates() {
//Sheet variables
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var values = sheet.getDataRange().getDisplayValues();
//Header variables
var headers = sheet.getDataRange().getValues().shift();
var idIndex = headers.indexOf('ID');
var nameIndex = headers.indexOf('Name');
var emailIndex = headers.indexOf('Email');
var subjectIndex = headers.indexOf('Subject');
var statusIndex = headers.indexOf('Status');
var fileNameIndex = headers.indexOf('File Name');
var filerIndex = headers.indexOf('Filer');
var birthIndex = headers.indexOf('Date of birth');
//Logger.log(statusIndex)
//Document Templates ID
var templateFile = DriveApp.getFileById(TEMPLATE_FILE_ID);
//Destination
var destinationFolder = DriveApp.getFolderById(DESTINATION_FOLDER_ID);
var templateTextHtml = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Email').getRange('D11').getValue();
//Run through the variables
for (var i=1; i<values.length; i++) {
//If first column is empty then stop
var index0 = values[i][0];
if(index0 == "") continue;
var rowElements = values[i].length;
var fileStatus = values[i][statusIndex];
//If the row already processed then stop
if (fileStatus == "Created") continue;
//If the row is not processed continue
//Define the new filename by the relevant Column
var fileName = values[i][fileNameIndex];
var newFile = templateFile.makeCopy(fileName, destinationFolder);
var fileToEdit = DocumentApp.openById(newFile.getId());
//Replace placeholders in the new document
for (var j=1; j<rowElements-1; j++) {
var header = values[0][j];
var docBody = fileToEdit.getBody();
var patternToFind = Utilities.formatString('{{%s}}', header);
docBody.replaceText(patternToFind, values[i][j]);
}
//Create the PDF file
fileToEdit.saveAndClose();
var newPdf = DriveApp.createFile(fileToEdit.getAs('application/pdf'));
DriveApp.getFolderById(DESTINATION_FOLDER_ID).addFile(newPdf);
DriveApp.getRootFolder().removeFile(newPdf);
newFile.setTrashed(true);
var newPdfUrl = newPdf.getUrl();
//Create the emailbody
var textBodyHtml = templateTextHtml.replace("{{Name}}",values[i][nameIndex]).replace("{{Date of birth}}",values[i][birthIndex]);
var textBodyPlain = textBodyHtml.replace(/\<br>/mg,"");
//Will send email to email Column
var email = values[i][emailIndex];
var emailSubject = values[i][idIndex]+" - "+values[i][fileNameIndex]+" - "+values[i][nameIndex];
MailApp.sendEmail(email,emailSubject,textBodyPlain,
{
htmlBody: textBodyHtml+
"<p>Automatic generated email</p>",
attachments: [newPdf],
});
sheet.getRange(i+1, filerIndex+1).setValue(newPdfUrl);
sheet.getRange(i+1, statusIndex+1).setValue('Created');
}//Close for (var i=1...
}
I want to copy the cell with the daily score from the sheets of all of my students in a spreadsheet where they are calculated and collected to another spreadsheet where they are used as currency to buy rewards. Both spreadsheets contain a sheet for every student that is named after that student, e.g. "John Smith"
The original google script that I created worked, but it was poor coding because I had to repeat the coding for every single name, and therefore add a new paragraph of code every time we get a new student. I would like to create a new google script that is more elegant and powerful and works without specifying the students' names so that it never needs to be amended. I can't quite get it and keep hitting a "Syntax error" with the last line.
function ImportDailyScore() {
var dailyinput = "J27"; // Mon=J3, Tue=J9, Wed=J15, Thu=J21, Fri=J27
var dollaroutput = "B2"; // Today=B2, Yesterday=B3, etc.
var dollarspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var checkinspreadsheet = SpreadsheetApp.openById('some id');
var checkinsheets = checkinspreadsheet.getSheets(); // get all the sheets from check in doc
var dollarsheets = dollarspreadsheet.getSheets(); // get all the sheets from dollar doc
for (var i=0; i<checkinsheets.length; i++){ // loop across all the checkin sheets
var checkindata = checkinsheets[i].getRange(dailyinput).getValue();
var namedcheckin = checkinsheets[i].getSheetName()
for (var j=0; j<dollarsheets.length; j++){
var nameddollar = dollarsheets[j].getSheetName();
if (namedcheckin = nameddollar, dollarsheets[j].getRange(dollaroutput).setValue(checkindata))
}
}
}
For reference, the original code (which works just as I would like it to) but needs to specify the name of every single student is:
function ImportDailyScore() {
var dollarspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var checkinspreadsheet = SpreadsheetApp.openById('1Y9Ys1jcm1xMaLSqmyl_pFnvIzbf-omSeIcaI2FgjFIs');
var dailyinput = "J3"; // Mon=J3, Tue=J9, Wed=J15, Thu=J21, Fri=J27
var dollaroutput = "B4"; // Today=B2, Yesterday=B3, etc.
var JohnCHECKIN = checkinspreadsheet.getSheetByName('John Smith');
var JohnCHECKINData = JohnCHECKIN.getRange(dailyinput).getValue();
var JohnDOLLAR = dollarspreadsheet.getSheetByName('John Smith');
JohnDOLLAR.getRange(dollaroutput).setValue(JohnCHECKINData);
var JenniferCHECKIN = checkinspreadsheet.getSheetByName('Jennifer Scott');
var JenniferCHECKINData = JenniferCHECKIN.getRange(dailyinput).getValue();
var JenniferDOLLAR = dollarspreadsheet.getSheetByName('Jennifer Scott');
JenniferDOLLAR.getRange(dollaroutput).setValue(JenniferCHECKINData);
etc.
Try this:
I don't have enough information to know how to handle the dollaroutput programmatically.
function ImportDailyScore() {
var inputA=['','J3','J9','J15','J21','J27',''];
var dailyinput = inputA[new Date().getDay()];
if(dailyinput) {
var dollaroutput = "B2"; // Today=B2, Yesterday=B3, etc. Not sure how to handle this programmatically I'd have to see the sheet
var dollarspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var checkinspreadsheet = SpreadsheetApp.openById('id');
var checkinsheets = checkinspreadsheet.getSheets(); // get all the sheets from check in doc
var dollarsheets = dollarspreadsheet.getSheets(); // get all the sheets from dollar doc
for (var i=0; i<checkinsheets.length; i++){ // loop across all the checkin sheets
var checkindata = checkinsheets[i].getRange(dailyinput).getValue();
var namedcheckin = checkinsheets[i].getSheetName();
for (var j=0; j<dollarsheets.length; j++){
var nameddollar = dollarsheets[j].getSheetName();
if (namedcheckin == nameddollar) {
dollarsheets[j].getRange(dollaroutput).setValue(checkindata);
}
}
}
}else{
SpreadsheetApp.getUi().alert(Utilities.formatString('Today is %s no school.', Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "E")));
}
}