iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Modern Web

整合 Google 服務的燃料——透過 Google Apps Script (GAS) 加速你的工作速度系列 第 18

D18 - 如何用 Apps Script 自動化地創造與客製 Google Docs?(五)Element 的更新

今天的目標

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

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

先來個小測驗。



答案在今天的文章當中!

前情提要一下

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

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

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

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

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

好,那我們就開始吧!

Step 1 從 Document 中進入 GAS

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

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

Step 2 設定好 getBody()

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

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

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

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

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

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

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())
  }
}

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

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

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

可以得知:

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

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

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())
  }
}

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

可以看出,只要中間有隔出一行,就會被視為不同的表單。那我們要如何讓就算有空一行,仍被視為同樣的表單呢?這時就要用到 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);
  }
}

跑起來長這樣——

可以發現,被認為是清單的物件數字全部變成連貫的了!(原本藍色的 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);
  }
}

跑起來長這樣——

那為什麼還有一個藍色的項目沒有變成子項目呢?因為 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();
    }
}

跑出來長這樣——

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();
    }
  }
}

跑起來長這樣——

我這邊示範了兩組數字,分別是 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("。","!");
  }
}

跑起來長這樣——

提醒的是,我這樣的指令碼,是針對 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)
  }
}

跑起來長這樣——

那可以發現:

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

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

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


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


上一篇
D17 - 如何用 Apps Script 自動化地創造與客製 Google Docs?(四)Element 的刪除與層級關係
下一篇
D19 - 如何用 Apps Script 自動化地創造與客製 Google Docs?(六)更改特定內容格式的 Attribute 操作技巧
系列文
整合 Google 服務的燃料——透過 Google Apps Script (GAS) 加速你的工作速度30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言