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。如果不想错过可以订阅按赞小铃铛(?),也欢迎留言跟我说你还想知道什麽做法/主题。我们明天见。


<<:  [第四天]从0开始的UnityAR手机游戏开发-介绍Unity介面和常用快捷键

>>:  #18 JS: Intro to function

第二十八天:用 TeamCity 发布 Package

在这系列教学里,我们以撰写一个以购物车为主题的 Kotlin 函式库为例,经过一连串 TDD、语法风...

Day-13 Pytorch Tensors

程序语言会有一些常见的资料组单位,例如 python 会有 list,C、C++ 有 array ...

[Day. 26] Codeigniter 页面

昨天我们对更新资料库的资料进行了简单的操作, 我们对资料库已经可以新增、更新了 我们现在的情境是 i...

Day15看鱿鱼游戏就要搭上鱿鱼料理-琉球菜鱿鱼小封

Netflix上的鱿鱼游戏正夯,雪伦也是一集接一集的看完了 上次看机智医生生活搭配辣炒年糕,那这次看...

【Day14】:STM32辗压Arduino的功能—TIM(上)

计时器 TIMER 今天开始我们要来使用STM32强大的功能之一 TIMER! STM32F429Z...