Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)

目標

要怎麼簡單快速地做出客製化地文件?今天,我們會教用 GAS 搭配 Goolge Doc。那因為在 Google Slide 中的 Element 也是相同的,所以這邊就會講細一點,之後就可以一起服用。換句話說,今天會教說怎麼透過 GAS 調整 Google Doc 和 Google Slide 裡面的元素。那今天的問題可以有以下的排列組合——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图

雖然總共有 4×4 共 16 種的排列組合,我們會用案例一個個來說明。基本上前天講了講新增與讀取,昨天專注於刪除,今天終於可以講到「更新」了,就讓我們開始吧!

先來個小測驗

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图1
Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图2
答案在今天的文章當中!

前情提要一下

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

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

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图3

那我們把 Element 展開來看,裡面有很多小的物件,這邊抓出其中最常用的四種。分別是段落、照片、表格與清單。

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图4

那我們的目標就是透過讀取、寫入、更新與刪除(對的,參照 CRUD 的 format)來帶大家讓是怎麼操作這些表格。這邊就節錄一本書中的「段落、照片、表格與清單」,來作為今天我們的範例。

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图5

這一集我們有個重要的的觀念,也就是我們可以透過 getParent() 和 getChildren() 來取得上下層關係的物件。如果這個關係不懂,一定要回到前一天看,因為今天會大量用到。完整的上下層概念,可以參考 Google 的官方文件

好,那我們就開始吧!

Step 1 從 Document 中進入 GAS

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

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图6

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

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图7

Step 2 設定好 getBody()

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

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

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

Q4. 如何更新 Google Docs 中的清單

那不管是刪除還是更新,我們都要先取得目標的物件。所以先讓我們取得清單,為此我們先做出三份清單。

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图8

接著,我們要讀取清單,並進行更動。

Step 3 用 getListItems() 讀取清單

接下來,我們試著用 getListItems() 來讀取清單的內容。

function readLists(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    Logger.log(list_items[i].getText())
  }
}

理論上跑起來會是清單含有的所有文字,但這是理論上。來確認一下跑起來如何——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图9

看起來沒抓到範圍,是怎麼回事?原因是getListItems() 要抓的是「Google Doc」認可的清單。而我只是打上點點或數字,並不代已經是它認可的清單格式,這個時候就會出問題。換句話說,我們要重新確認一下自己的文本。以下示範如何調整成正確的格式——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图10

好,那實際運作的 getListItems() 會跑出什麼樣子呢?(注意,影片中的清單為了說明,有再微調)

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图11

可以得知:

  1. 只要是表格,無論是數字的還 bullet point 的都會被算算進去
  2. 就算是有階層( 1 > a > i),也會一視同仁地讀到

也附上小測驗第一題的答案。

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图12

Step 4-1 用 getListId() 和 setListId() 設定清單項目的歸屬

要注意 getListItems() 得到的不會是「有幾份清單」,而是有幾個清單的子項目。截稿的目前 GAS 未開放可以直接操作的 List Item。那,我們要怎麼知道這些項目屬於相同的 List 呢?這時要用到 getListId() 來核對。

function getListItemsParentTable(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    Logger.log(list_items[i].getText() +" "+list_items[i].getListId())
  }
}

這邊我們直接用圖解,說明執行後的結果。

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图13

可以看出,只要中間有隔出一行,就會被視為不同的表單。那我們要如何讓就算有空一行,仍被視為同樣的表單呢?這時就要用到 setListId() 了。假如我想用一段程式碼,讓圖中的所有合格清單項目,都歸屬於同一個清單。

function setListItemsSameList(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  let item_1 = list_items[0];
  for (let i=0; i < list_items.length; i++){
    list_items[i].setListId(item_1).setGlyphType(DocumentApp.GlyphType.NUMBER);
  }
}

跑起來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图14

可以發現,被認為是清單的物件數字全部變成連貫的了!(原本藍色的 1 2 3 變為 5 6 7)那這邊有兩點要注意。

  1. setListId() 後面要放的 不是 ID,而是你想要設定跟它作為同一張表的 ListItem,以上面的程式碼來說,我就以第一個 item_1 作為歸屬處。
  2. 預設會都是 bullet,所以我這邊提早借用了 Attribute 的 .setGlyphType(DocumentApp.GlyphType.NUMBER) 來讓 List 可以是數字為基底。

有人問說為什麼紅色的範例清單沒有動,因為它不是 Google Doc 認可的清單,在上頭的 Step 3 的有提到原因與改進方式。

好,那總算搞定清單的讀取了,接著就讓我們用來玩「更新內容」吧!

Step 4-2 用 setNestingLevel(nestingLevel) 設定層級

在清單中,我們很常用 Tab 鍵來調整層級,而這功能在 GAS 中即是 setNestingLevel(nestingLevel) ,預設的層級即為 0。那我們來看如何設置從 0 開始的層級。

function setListItemLevel(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    Logger.log(i)
    list_items[i].setNestingLevel(i);
  }
}

跑起來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图15

那為什麼還有一個藍色的項目沒有變成子項目呢?因為 nestingLevel 的最大值是 8,也就是我們最多只能創造九層(0 ~ 8)。

Step 4-3 用 removeFromParent() 將單一 ListItem 拔除

如果我們只想移除其中特定的 Item,不想影響其上或下階層的內容,那我們可以用 removeFromParent() 。抓出想移除的 ListItems,將其移出。

function removeItemListFromParent(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    if (i==7){
      list_items[i].removeFromParent();
    }
}

跑出來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图16

Step 4-4 用 merge() 將兩個 ListItem 合併

如果今天我們想將層級不同的清單(List)進行合併,要怎麼做?這時可以透過 Merge。這邊我們試著合併範例清單二的第一項(區別技術性與調適性挑戰,ListItemIndex = 7)和第三項(聆聽選外之音,ListItemIndex = 9)

function mergeListItemss(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    if (i==7){
      list_items[i].merge();
    }
  }
}

跑起來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图17

我這邊示範了兩組數字,分別是 i==7 & i==9 ,可以發現其合併的邏輯是:「與下面一項合併」,更細節來說,是

  1. 將下面一項(不論層級)的內容加到自己本身的內容之後。
  2. 將自己的層級變得跟下面一項一樣。

Step 4-5 用 replaceText() 更改文字內容

那總算到我們的文字部分拉, replaceText(parttern, replacement) 其實在前幾章就有偷用到,這邊完整講述一下使用方式。

  1. 首先 pattern 的部分是要用所謂的「正規表示法」,那部分我們再找時間細講,我自己的建議是直接寫上「要改的文字內容」;
  2. 再來 replacement 則要放你想換上的東西,可以理解成「另外的文字」。當然進階版你要換成圖片或其他物件其實是可以的。

那我們來看看怎麼用,這邊我就單純示範將句號換成驚嘆號的方式——

function replaceText(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    list_items[i].replaceText("。","!");
  }
}

跑起來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图18

提醒的是,我這樣的指令碼,是針對 ListItem 進行更改,文中其他的段落或表格都不會被動到。

Step 4-6 用 insertText() 插入文字內容

那這邊我們就實作一個用 insertText(childIndex, text) 在每個 ListItem 的之前,插入一個數字。

function readLists(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let list_items = doc_body.getListItems();
  for (let i=0; i < list_items.length; i++){
    list_items[i].insertText(0, i)
  }
}

跑起來長這樣——

Google Apps Script 自動化地創造與客製 Google Docs(五)Element 的更新 (2021 鐵人賽 D18)插图19

那可以發現:

  1. 從在字串前的 0.0 和 5.0 的數字呈現白色,而其他加上的呈現自己的顏色來看。如果「加入的內容」出現在最一開始,格式會參照其前一段落的設定。
  2. 當設定 childIndex 數字為 0 時,會插入在列點之後,文字之前
  3. 當設定 childIndex 數字為 1 時,會插入在文字的最後

那如果設定數字為 2 的話,各位客官可以自己跑一次看看,會出現錯誤。因為對 ListItem 來說,下面唯一的 Child ,就是列點內的內容。

今天學的大多都可以套用到「段落」與「表格」,不過要搭配前面兩天一起消化。那如果是非文字部分像是照片、圖表,基本上更新的方式建議直接刪掉舊的,加上新的。其他的今天的內容應該都可以 Cover。


好,那今天就到這邊。今天我們主要交代了 Element 的「如何更新」;如果還有問題,透過留言之外,也可以到 Facebook Group,想開很久這次鐵人賽才真的開起來,歡迎來當 Founding Member。如果不想錯過可以訂閱按讚小鈴鐺(?),也歡迎留言跟我說你還想知道什麼做法/主題。我們明天見。

目錄

Scroll to Top