iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
Modern Web

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

D16 - 如何用 Apps Script 自動化地創造與客製 Google Docs?(三)Element 的讀取與創造

今天的目標

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

雖然總共有 4x4 共 16 種的排列組合,我們會用案例一個個來說明。基本上今天會先講新增與讀取,明天會講更新與刪除,就讓我們開始吧!

先來瞭解 Google Docs 的架構

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

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

完整的架構概念,可以參考 Google 的官方文件

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

那在開始前,先來設定一下...

Step 1 從 Document 中進入 GAS

那這次我們不會用 Google Sheet,而是直接用 Google Doc 進入。

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

Step 2 設定好 getBody()

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

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

如果是要對 header 或 footer 做就將上面改成 getHeader()getFooter()

如何讀取 Google 文件中的段落(Paragraph)?

Step 3 讀取文件中的段落

基本上在 Google Doc 中,只要有看到文字,外面就會包一層段落。

換句話說,在看到文字時有分成外層的「段落」(以 getParagraph() 獲得)與段落內的「文字」(以 getText() 獲得)。我們先直接用一段簡單的 Code 來看執行起來會長什麼樣子。

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

跑起來長這樣——

可以發現,實際上讀取出的 Paragraph 中的文字,會連表格、清單內的一起讀出來,這是因所有「文字」外面的一層「段落」。另外值得注意的是,「空白列」也算是「段落」,理解上,可以想像在我們打字時,只要打字時按下「Enter」的部分,就是在創造一個新的 Paragraph,包括斷行的空白。

好,那要如果要讀取的話,基本上要用 getText() 但不能直接對全部的 paragraphs 直接用。而是要將每一個 paragraph 讀出再使用。

function readParagraph(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  // (X)let paragraphs = doc_body.getParagraphs().getText();
  let paragraphs = doc_body.getParagraphs();
  for (let i=0;i<paragraphs.length;i++){
    Logger.log(paragraphs[i].getText())
  }
}

讀起來像是這樣子——

可以從影片中發現,對 Google Doc 來說,「表格內的每一個、清單內的每一項都是一個 Paragraph Object (我們才會讀取到)。補充的是,表格內每一段要放文字的,外面都有再包一層 paragraph ,使用時要再小心。

好,那就順便補充基本四大檔案讀取方式。

使用方式是只要將上方的 getParagraph() 換成四大檔案的讀取,就可以讀取了。而特定段落、表單的讀取,基本上都是先用這四大讀取方式來取得再進行調整。

補充:findElement() 的使用說明

有些夥伴說問說有看到一個 API 叫 findElement() ,能用它來抓特定檔案嗎?

我們先來看一段程式碼。請大家在往下看跑起來會長什麼樣子之前,想一下,這段會有什麼樣的結果輸出?

function searchParagraph(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let paragraphType = DocumentApp.ElementType.PARAGRAPH;
  let paragraphs = doc_body.findElement(paragraphType).getElement();
  Logger.log(paragraphs.getText());
  for (let i=0;i<paragraphs.length;i++){
    Logger.log(paragraphs[i].getText())
  }
}

那跑起來是長這樣——

可以發現,只有抓出第一個段落,那為什麼?這個 findElement() 的 API 確實可以幫我們抓到元素(Element),但不一定都那麼適合用,這個 API 被設計為,你想要抓「特定物件」中的「特定元素」。所以舉凡

  1. 在 body 中抓 paragraph
  2. 在 header 中抓 image
  3. 在 table 中抓 list

都可以運用這個 API。但你會發現,這個 API 主要的目的是當「特定物件」不是常用的大架構如 Header、Body 與 Footer 的時候,要是在 Table 中抓 List,或是 Table 中抓 Table 才比較有用。不然我們可以很簡單地套用 getParagraph() getImages() 等 API 直接取得。

此外, findElement() 有兩種寫法。一種是 findElement(type) ,這種寫法幫助有限,原因是因為它就是直接抓出你的「第一個」段落,不會讓你讀取第二、第三段,除非你設定一個範圍。也就是我上方那段程式碼與影片所呈顯的。而第二種寫法,另一種則是 findElement(type, range),這會牽扯到 range 。而 range 的取得方式如下圖。

function buildRange(){
    let doc = DocumentApp.getActiveDocument();
    let rangeBuilder = doc.newRange();
    let tables = doc.getBody().getTables();
    for (let i = 0; i < tables.length; i++) {
        if (tables[i].getNumChildren() == 4){
          rangeBuilder.addElement(tables[i]);
        }
    }
    return rangeBuilder.build()
}

function findImageInTablesWithFourRows(){
    let doc_body = DocumentApp.getActiveDocument().getBody();
    let imageType = DocumentApp.ElementType.IMAGE;
    let target_table = doc_body.findElement(imageType, buildRange()).getElement();
    // your function
}

這段程式碼的意思是,我用 getRange() 抓出所有高度是四列的 Table,然後再用 findElement 回傳裡頭含有的圖片。但其實我們想用 findElement 做到的是,已經可以直接用 buildRange 那段的程式碼抓到(直接對 table[i].children() 中去尋找 Type 等於 Image者),換句話說,再多寫一段有點多此一舉。但並不代表 getElement 很廢,如果你想考慮的是當使用者在用滑鼠標記特定的文字段落時會很有用,這我們之後再聊。

而接著,如果我們想將照片寫入的話,又要怎麼做?

如何寫入 Google 文件中的照片(Image)?

在 Google Document 中,「加入」內容主要會分成:

  1. 新增:append()insert :在指定位置的前或後加入跟原本種類不同的(像是想在文字中插入圖片)
  2. 更新:主要是改文字內容,那就用 set() 更改文件、內容,在下一段會講到。

那在圖片的部分,又主要分成兩種:

  1. 跟文字排列的 InlineImage
  2. 漂浮於文字之上 or 下的 PositionedImage

好,那為了說明方便,我將原本的圖片上傳到 Google Drive 並取得其 ID。我們先來看看用 appendImage() 會長怎麼樣子。

function addImage(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let image = DriveApp.getFileById(example_image_ID).getBlob();
  doc_body.appendImage(image)
}

跑起來長這樣——

會發現圖片確實有加入,且是預設的 InlineImage 但變得好大!所以我們要:

  1. 抓出其尺寸比例 (image.getWidth() / image.getHeight()
  2. 並依照頁面的寬度(doc.getAttributes()['PAGE_WIDTH']
  3. 去進行調整 (image.setWidth() / image.setHeight()
function addImage(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let image = DriveApp.getFileById(example_image_ID).getBlob();
  let append_image = doc_body.appendImage(image)

  let page_width = doc_body.getAttributes()['PAGE_WIDTH'];
  let image_ration = append_image.getHeight() / append_image.getWidth();
  let image_new_width = page_width;
  let image_new_height = image_new_width * image_ration;
  append_image.setWidth(image_new_width);
  append_image.setHeight(image_new_height);
}

執行起來跑這樣——

那其他部分的執行,大致上程式碼在這,我們會找時間補齊的(遙望)

同場加應:如何寫入 Google 文件中的清單(List)?

有遇到夥伴在寫其他的時候遇到挑戰,這邊就一起示範一下怎麼用。 List 的部分就是不斷用 Append List 來進行,直接上示範。

function addList(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let item1 = doc_body.appendListItem('Item 1');
  let item2 = doc_body.appendListItem('Item 2');
  item2.setNestingLevel(1)
  let item3 = doc_body.appendListItem('Item 3');
  item3.setNestingLevel(3)
}

那執行起來長這樣——

同場加應:如何寫入 Google 文件中的表單(Table)?

一起示範怎麼寫表單。表單的邏輯跟 Google Sheet 一樣,是寫入 array in array。就直接示範了~

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

跑起來長這樣——

同場加應:如何寫入 Google 文件中的段落(Paragraph)?

而段落最簡單,這邊就直接放上程式碼囉,應該可以很快套用。

function addParagraph(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  doc_body.appendParagraph("Sentences you want to share")
}

那跑起來長這樣——

結論

好,那今天我們主要教了如何讀取與寫入,考量到篇幅,更新與刪除我們留到之後分享;如果還有問題,透過留言之外,也可以到 Facebook Group,想開很久這次鐵人賽才真的開起來哈哈哈,歡迎來當 Founding Member。如果不想錯過可以訂閱按讚小鈴鐺(?),也歡迎留言跟我說你還想知道什麼做法/主題。我們明天見。


上一篇
D15 - 如何用 Apps Script 自動化地創造與客製 Google Docs?(二)快速生出大量寄件信封資料
下一篇
D17 - 如何用 Apps Script 自動化地創造與客製 Google Docs?(四)Element 的刪除與層級關係
系列文
整合 Google 服務的燃料——透過 Google Apps Script (GAS) 加速你的工作速度30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言