Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)

目標

要怎麼抓出文件中的特定文字或段落,直接改字體的大小、顏色、背景、粗細與字型?先來看今天的結果之一,把文章中的「人」字都改成紅字、粗體與放大——

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图

今天,我們會教用 GAS 搭配 Goolge Doc 來設定,這個要動用到的元素叫做 Attribute。那因為在 Google Slide 中的 Element、Attribute 也很多有重疊,所以這邊就會講細一點,之後就可以一起服用。換句話說,今天會教說怎麼透過 GAS 調整 Google Doc 和 Google Slide 裡面元素的「格式」。我們先複習一下前幾天講過的議題——

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图1

那今天,我們會針對上述議題中的「元素」進行。基本上前幾天講了上述元素的操作,今天終於可以進入到「格式調整」了,今天會專注在以下內容——

  1. 如何更改 Google Document 中元素的樣式?

就讓我們開始吧!

前情提要一下

考慮到有些夥伴不一定會都有細讀的文章,就撈叨一點把基本步驟再次附上,如果會的夥伴可以直接跳到 Q5。

我們已經知道大致上,每一個 Google 文件都會有 Element (元件),且每一個 Element 都會有 Attribute (屬性)。今天我們主要會介紹下圖綠色的 Attribute 的部分。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图2

下圖主要是列舉幾個常見用於操作 Element 的 Attribute。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图3

這邊就節錄一本書中的「段落、照片、表格與清單」,來作為今天我們的範例。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图4

好,大致理解基本概念後,就讓我們開始吧。

Step 1 從 Document 中進入 GAS

那這次我們不會用 Google Sheet,而是直接用 Google Doc 進入,借一下 D16 的影片。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图5

一樣第一次會有存取驗證需要大家按一下。這邊仍是借用一下 D2 的影片。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图6

Step 2 設定好 getBody()

我們先用 getActiveDocument() 抓出正在綁定的文件;那假設我們都是針對主要內文(Body)的部分,所以我們先設定好 getbody()

let doc_body = DocumentApp.getActiveDocument().getBody();

因為更新有比較複雜的細節,我們就先來講講刪除。

Q5. 如何用 GAS 設定 Google Doc 的樣式?

基本上之後的步驟會分成:

  1. 讀取/新增目標元素(Element)
    1. 全部讀取
    2. 新增元素
    3. 部分讀取(Specific Range)
  2. 讀取現有屬性(getAttributes()
  3. 更改現有屬性(setAttributes()

Step 3 讀取/新增目標元素(Element)

3-1 和 3-2 主要就是 D16 Element 的讀取與創造 的涵蓋內容,這邊就一樣快速帶給大家看。

Step 3-1 全部段落讀取

這邊就先上一段「讀取」的程式碼——

function readParagraph(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let paragraphs = doc_body.getParagraphs();
  Logger.log(paragraphs)
}

再來看讀取的結果影片——
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图7

那如果要改成讀取其他的 Element,可以參考之前的簡易表單來做更換。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图8

Step 3-2 新增元素

那如果我們今天要新增一段表格,可以怎麼做?我們可以運用 appendTable 的功能來執行。

function addTable(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let cells = [[1,2,3],[4,5,6]];
  doc_body.appendTable(cells)
}

跑起來長這樣——
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图9

那如果要改成新增其他的 Element,可以參考之前的簡易表單來做操作。
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图10

那以上這邊如果覺得太快,或想知道更細部,記得可以回去看 D16 Element 的讀取與創造 ,如果覺得時間OK,那我們就進入到新的部分,也就是「部分讀取」。

Step 3-3 部分讀取元素

比起整段做讀取、更改,我們更常遇到的情況是只要更動部分。像是我想要把文件中的關鍵字抓出來標記顏色等、或是想要動從第三頁到第八頁的表格,詳細要怎麼執行?

基本上第一部,我們都要先跟 GAS 說「我想抓出元素的___部分」。而這邊就要用 findText() 來執行。

findText() 的基本使用架構如下,我們用來示範如何抓到段落中的「人」字。

function testFindText(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let target = '人';
  let searchResult = doc_body.findText(target);

  while (searchResult !== null) {
    Logger.log(searchResult.getElement().asText().getText());
    searchResult = doc_body.findText(target, searchResult);
  }
}

用法上, findText(searchPattern, from) 前面的 searchPattern 用的是 regex(嚴格說起來是 google 的 re2),我們就簡單輸入要找的文字;後面的 from 指的是當找到多個元素後,從哪一個開始?沒輸入的話預設會回傳第一個,但在第一個之後如果要繼續找,就要搭配 while 回圈並將 from 輸入為前一次的搜尋結果。換句話說,就是把下圖手刻的方式轉成 while 迴圈。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图11

跑起來結果長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图12

那,從清單的只有回傳兩點發現,它確實有抓到「人」字,但是是回傳了「人」字所在的整個元件(Element),如果是在段落(Paragraph)內,就回傳段落,如果是在清單項目(ListItem)內就回傳整個項目。那我們要怎麼抓出特定範圍?這時就要提到 range 元素了。

range 元素與 offset 的概念

Range 是什麼?簡單來說,當我們用滑鼠選取了一段內容後,都會伴隨出range (當然省略掉 select 的部分,不過在這邊我們先注重在 range)。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图13

且每個 range 都有被伴隨的 offset(位移),簡單來說可以當成:是在段落中的哪個位置。

以上面的影片來說,「人」這個字即是位在第四個位置(Offset 為 3);更嚴格地說,「人」這個 range 的在段落中的開始位置(StartOffset) 和結束位置(EndOffset)都是為在 Offset 為 3的部分。

注意因為是程式語言,第一個位置我們要從 Offset 為 0 開始算。位移的概念可以參考如下

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图14

那為什麼這個重要?因為當我們等下要改內容時,這就會派上用場。實際上,對 GAS 來說,「部分選取」就是先選整段,再跟我說段落中的哪個位置(Offset)要改。我們快速示範如果要將 Offset 的文字都改成紅色要怎麼做。

我們先來看看沒上 Offset 的結果,會發現就等於整段改成紅色。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图15

同時看看上了 Offset 的結果,會發現只有我們要的「特定範圍」變色。
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图16

實際上,我是透過 range 物件中的 getStartOffset() 和 getEndOffsetInclusive() 達到的。先附上程式碼,可以先專注看「Offset」的部分,我們馬上就會來細講改顏色的部分。

function highlightText() {
  let target = '人';
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let searchResult = doc_body.findText(target);
  let textStyle={};
  textStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = '#FF0000'

  while (searchResult !== null) {
    let search_text = searchResult.getElement();
    Logger.log(searchResult.getStartOffset() +' '+ searchResult.getEndOffsetInclusive())
    search_text.setAttributes(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),textStyle)
    searchResult = doc_body.findText(target, searchResult);
}

好,那我們能抓到範圍了,接下來就是要改屬性了。但在改屬性前,因為物件的屬性算有點複雜,我個人是會建議先檢查「有沒有這個屬性」。

Step 4 用 getAttributes() 來讀取現有屬性

通常我會先用 getAttributes() 來看我的目標物件有哪些可以調整的屬性。這也可以幫助我們理解怎麼設定屬性。這邊寫一段簡單的程式,會印出目標「段落」、「清單項目」和「文件」的含有屬性。

function testFindText(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let target = '人';
  let searchResult = doc_body.findText(target);

  while (searchResult !== null) {
    Logger.log(searchResult.getElement().asText().getText());
    Logger.log(searchResult.getElement().getAttributes());
    searchResult = doc_body.findText(target, searchResult);
  }

  Logger.log(doc_body.getAttributes())
}

跑起來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图17

我這邊把「段落」、「清單項目」與「文件」分開來看。

  1. 「段落」部分:
{STRIKETHROUGH=null, LINK_URL=null, FONT_FAMILY=Source Sans Pro, FONT_SIZE=null, UNDERLINE=null, BACKGROUND_COLOR=null, ITALIC=null, FOREGROUND_COLOR=null, BOLD=null}
  1. 「清單項目」部分
{LINK_URL=null, FOREGROUND_COLOR=null, BOLD=null, BACKGROUND_COLOR=null, UNDERLINE=null, STRIKETHROUGH=null, FONT_FAMILY=Source Sans Pro, FONT_SIZE=null, ITALIC=null}
  1. 「文件」部分
{MARGIN_TOP=21.25984251968504, LINK_URL=null, FONT_SIZE=null, UNDERLINE=null, STRIKETHROUGH=null, FOREGROUND_COLOR=null, FONT_FAMILY=null, BOLD=null, PAGE_HEIGHT=841.68, MARGIN_BOTTOM=72.0, MARGIN_LEFT=72.0, BACKGROUND_COLOR=null, PAGE_WIDTH=595.4399999999999, ITALIC=null, MARGIN_RIGHT=72.0}

這邊要特別提一下我們抓出的資料結構,是一種叫做 dictionary 的結構。這邊的重點會放在,所以如果我們要寫入「屬性」,我們也要套用這結構。

而其中的屬性有三種特性——

  1. 越是上層(Parent)的物件,擁有的屬性越多。子元素有的屬性上層基本上都會有。
  2. 屬性中的順序不一定,但都是種 Key-value Pair(如果想知道原因,主要是因為資料結構是 Dictionary,其內部順序不是重點)
  3. 跟 CSS 階層一樣,下層寫死的屬性會蓋過上層的。這點應該算好理解,因為你設定全文為黑色時,仍是可以設定部分文字為紅色。

特別把第一點拉出來說明,因為待會會用到這概念。
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图18

Step 5 用 setAttributes() 更改現有屬性

好,那到底要怎麼改屬性呢?我們先來一段程式碼。

function setElementAttribute(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let style = {};
  style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#FF0000'
  let paragraphs = doc_body.getParagraphs();
  for(para of paragraphs){
    para.setAttributes(style);
  }
}

跑起來長這樣——
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图19

這邊先設定一個 dictionary 叫做 style (let style = {}),並用 style[key]=value 設定期中的數值(精確來說是 Key-value pair),其中 key 的部分就是我們從 Step 4 中抓出來的屬性們,只是前面多加了 DocumentApp.Attribute ;數值就是 Step 4 的數值們。

可以發現,就是將所有「段落」都改成紅色。至於為什麼「清單」也會變色,複習是因為清單內的文字,其實也有包含一個段落的元素。好,那這是整段的更改,基本上就是要全部元素取得後,針對一個個元素抓出來設定。

那如果我們想做部分文字的更改呢?這邊就可以用我們在 Step 3 最後面的段落,先貼上方便大家比較。

function highlightText() {
  let target = '人';
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let searchResult = doc_body.findText(target);
  let textStyle={};
  textStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = '#FF0000'

  while (searchResult !== null) {
    let search_text = searchResult.getElement();
    Logger.log(searchResult.getStartOffset() +' '+ searchResult.getEndOffsetInclusive())
    search_text.setAttributes(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),textStyle)
    searchResult = doc_body.findText(target, searchResult);
}

基本上就是要在 setAttributes() 前加上「起始」與「結束」的 Offset,跑起來長這樣。
Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图16

好,那我們介紹了「單純改字體顏色」,如果我們想調整的很多,要怎麼辦?沒問題的,這邊直接上程式碼,用「部分元素」做示範。

function highlightText() {
  // target,background
  let target = '人';
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let searchResult = doc_body.findText(target);
  let textStyle={};
  textStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = '#FF0000'
  textStyle[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
  textStyle[DocumentApp.Attribute.FONT_SIZE] = 18;
  textStyle[DocumentApp.Attribute.BOLD] = true;

  while (searchResult !== null) {
    let search_text = searchResult.getElement();
    search_text.setAttributes(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),textStyle)
    searchResult = doc_body.findText(target, searchResult);
  }
}

跑起來長這樣,確定我們有改到「人」字的顏色、字體與粗體——

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图

但如果想設定其他的參數呢?這邊直接幫大家整理了表格。首先,如果是要改「部分文字」,可以用以下的功能們。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图20

如果是要改「整個段落」,可以用以下的功能們。

Google Apps Script 自動化地創造與客製 Google Docs(六)更改特定內容格式的 Attribute 操作技巧 (2021 鐵人賽 D19)插图21

如果是要改「文字」以外的部分,可以參考官方文件

好,那這邊就是我們今天的內容。我有參考這篇:Can I color certain words in Google Document using Google Apps Script?,如果有夥伴想將上述功能改成有 UI 的 Add-On,裡面有完整的程式碼。另外,官方也有文件說明 Editing and styling text,也可以搭配參考。


好,那今天就到這邊。今天我們主要交代了 Attribute 的「如何更新」,總算把 Document 的部分告一段落,明天會進入 Slide 的部分,最後會講 Sheet。如果還有問題,透過留言之外,也可以到 Facebook Group,想開很久這次鐵人賽才真的開起來,歡迎來當 Founding Member。如果不想錯過可以訂閱按讚小鈴鐺(?),也歡迎留言跟我說你還想知道什麼做法/主題。我們明天見。

目錄

Scroll to Top