每逢過年過節,不時會收到些禮物或送出些禮物,但要怎麼樣依據不同的對象,來客製化我們的內容。尤其當超過百人要送禮時,又要怎麼簡單快速地做出文件?這時可以考慮用 GAS 搭配 Google Sheet 與 Goolge Doc。那,就讓我們看看如何「只調整 Google Doc 中的一點點部分」?
今天只有一個問題,昨天我們回答了如何簡單客製化,今天我們會用「複雜範本」當案例,那就讓我們就開始吧!
今天我們的情境是,要大量產生給客戶的內容;像是我們最近要準備中秋的禮品們,請問要怎麼自動化大型的標籤?可以用 Google Sheet 拉,但如果想要大一點,用半張紙來排版,能怎麼做?
好,那因為我們要用到 Google Sheet ,所以一樣用其作為開啟的管道。一樣借用 D8 的影片。
一樣執行時會有「需要驗證」出現,借用一下 D2 的影片。
關於創造文件,我們可以選擇複製範本(像是 D11),或是創造新的文件(像是 D12);昨天我們講解了「創造新文件」,這篇會針對「複製範本」來做介紹。那現在一樣抓起範本的 ID,而以下是我們抓 ID 的兩種方式——
每一個 Google 產品都有特定的 ID,可以用我們上述的方式簡單取得,如果想複習,在 D9 有完整的介紹可以參考。
那我們做一個範本如下——
有看到收件者、地址與電話嗎?我這邊直接用一個變數代稱,分別是 receiver、address 和 phone number,晚點這三個變數都用到。
將範本的 ID 抓出。
var template_doc_id= "your_id_here"
完整取得 ID 的方式在 D9 有詳細的介紹可參考:如何用 Google Apps Script 自動化對 Google Drive 的操作?(一)列出所有檔案 ID 與相關資訊
那我們要怎麼做到複製範本並調整呢?
要先提到的是 Google Doc 中主要分成兩大物件,一個是 Element,主要就是文字、影像、段落、表格等等的「純內容」、骨架。一個是 Attribute,包含字體大小、排版、顏色、粗斜體等等,是 Element 的外皮、外衣與皮膚 。(概念上,有點像是 html element 和 css style )。今天我們會主要講 ElementType 的部分。
這邊先用一段程式碼示範給大家看。
function createDocFromTemplate(){
let row_data = ['101','Amy','經理','0911111111','','House of Amy']
let doc_file = DriveApp.getFileById(template_ID);
let title = row_data[0] + " " + row_data[1] + " " + row_data[2];
let contact = '';
let new_doc_Id = doc_file.makeCopy(title).getId()
let new_doc = DocumentApp.openById(new_doc_Id);
let doc_body = new_doc.getBody();
if (row_data[3]){
contact= row_data[3]
}else{
contact= row_data[4]
}
Logger.log(title+ " "+ contact+ " "+ row_data[5])
doc_body.replaceText("receiver", title);
doc_body.replaceText("address", row_data[5]);
doc_body.replaceText("phone number", contact);
}
這段程式碼的意思是——
['101','Amy','經理','0911111111','','House of Amy']
。101 Amy 經理
。file Object
用 .makeCopy(title)
的,如果對 Drive 不熟,可以參考 D8。DocumentApp.openById
改成以 Document Object
操作。並用其抓出 body()
這邊跟昨天的 D14 一樣。row_data[3]
,一種是市話 row_data[4]
,一種是沒號碼。我寫了一段 for 迴圈,用意是想說當有手機的話,聯絡資訊就留手機,沒手機就留市話(是話與手機都沒有的話,會填預設的市話 (row_data[4]
==""),也就是空白的字串。replaceText
來取代掉 Template 裡面的文字。第六小點我多說明一些。我們使用的是 body Object 的 method,完整的寫法是 replaceText(searchPattern, replacement)
。
pattern
,實際上是 regex 的表示法,如果不熟悉,可以當成「變數」來想像。 但實際上功能很強大,可以搜尋特字元。好,那同時在範本設定變數中,要小心設定,別設定文中可能有的文字,不然在沒有設定好 Pattern
的情況下會一起被更換。replacement
內容。基本上就是文字我們來看跑出來的結果——
好,那看起來沒問題,那我們要怎麼大量執行。
那我們要長出什麼樣子呢?這邊先給大家看我們今天的參數。
接下來的步驟是:
一樣,先來看看完整程式碼。
var template_ID = "your_doc_template_id_here";
// 從 Google Sheet 的 B2 開取到 G7
function readSheetData(){
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getActiveSheet();
let start_row = 2;
let start_col = 2;
let numRows = sheet.getLastRow() - start_row +1;
let numCols = sheet.getLastColumn() - start_col +1;;
let values = sheet.getRange(start_row,start_col,numRows,numCols).getValues();
return values;
}
// 轉接「讀變數」與「寫文件」
function writePages(){
let data = readSheetData();
for (row_data of data){
createDocFromTemplate(row_data);
}
}
// 微調的「寫文件」功能
function createDocFromTemplate(row_data){
let doc_file = DriveApp.getFileById(template_ID);
let title = row_data[0] + " " + row_data[1] + " " + row_data[2];
let contact = '';
let new_doc_Id = doc_file.makeCopy(title).getId()
let new_doc = DocumentApp.openById(new_doc_Id);
let doc_body = new_doc.getBody();
if (row_data[3]){
contact= row_data[3]
}else{
contact= row_data[4]
}
Logger.log(title+ " "+ contact+ " "+ row_data[5])
doc_body.replaceText("receiver", title);
doc_body.replaceText("address", row_data[5]);
doc_body.replaceText("phone number", contact);
}
取得資料部分,如果不懂為什麼有這樣的架構、要這樣寫,可以參考 D4;寫資料部分,則就單純是把上面的 Step 3 改一下。
最後跑出來長這樣——
好,那今天就完成了!事後有朋友問,那如果想要將很多檔案合併起來怎麼辦?不想要這麼分散。
這邊就直接做給大家看。簡單來說,我們創造一份新文件(DocumentApp.create()
),然後把每一份從 Template 複製的內容(template_file.getBody().copy()
)改完後丟回原本的新文件,再加上分頁 (appendPageBreak()
)。
function moveFile(fileId, destinationFolderId) {
let destinationFolder = DriveApp.getFolderById(destinationFolderId);
DriveApp.getFileById(fileId).moveTo(destinationFolder);
}
function writePages(){
let data = readSheetData();
let new_doc_Id = DocumentApp.create("all_docs").getId();
moveFile(new_doc_Id,folder_ID);
let template_file = DocumentApp.openById(template_ID);
let new_doc = DocumentApp.openById(new_doc_Id);
for (row_data of data){
mergeDocFromTemplate(row_data, new_doc, template_file);
new_doc.appendPageBreak();
};
}
function mergeDocFromTemplate(row_data, new_doc,template_file){
let title = row_data[0] + " " + row_data[1] + " " + row_data[2];
let contact = '';
let copied_body = template_file.getBody().copy();
if (row_data[3]){
contact= row_data[3]
}else{
contact= row_data[4]
}
Logger.log(title+ " "+ contact+ " "+ row_data[5])
copied_body.replaceText("receiver", title);
copied_body.replaceText("address", row_data[5]);
copied_body.replaceText("phone number", contact);
let totalElements = copied_body.getNumChildren();
for( let j = 0; j < totalElements; ++j ) {
let element = copied_body.getChild(j).copy();
new_doc.appendParagraph(element);
}
}
那跑起來長這樣——
比較特別要提的是,Google Docs 裡面每一個段落、文字、影像等都是一個物件,這也是為何我們會需要先透過 .getNumChildren()
來抓出要複製的內容,再一個個的用 appendParagraph()
貼上。提醒的是,所謂 paragraph
對 word 來說,是按下一次 enter
就會算創造一個新的段落。
我們這邊用的 appendParagraph()
是「僅限文字段落」的複製,如果要複製影像、Table、List 等其他格式等,可以參考這篇來實作: How to Merge Multiple Google Documents
今天的主題是為了應景中秋節看夥伴很辛苦地用名單,順手寫出來的。想說就趕緊先上應用版,明天再上完整辭典版。
一樣提醒的是,創造文件有 Quota 限制——每天不超過 250 件。好,那今天就是我們的 D15,明天我會介紹要怎麼樣操作更多的 Element。
如果還有問題,透過留言之外,也可以到 Facebook Group,想開很久這次鐵人賽才真的開起來哈哈哈,歡迎來當 Founding Member。如果不想錯過可以訂閱按讚小鈴鐺(?),也歡迎留言跟我說你還想知道什麼做法/主題。我們明天見。