D17 - 如何用 Apps Script 自动化地创造与客制 Google Docs?(四)Element 的删除与层级关系

今天的目标

要怎麽简单快速地做出客制化地文件?今天,我们会教用 GAS 搭配 Goolge Doc。那因为在 Google Slide 中的 Element 也是相同的,所以这边就会讲细一点,之後就可以一起饮用。换句话说,今天会教说怎麽透过 GAS 调整 Google Doc 和 Google Slide 里面的元素。那今天的问题可以有以下的排列组合——

虽然总共有 4x4 共 16 种的排列组合,我们会用案例一个个来说明。基本上昨天讲了讲新增与读取,今天会专注於删除,就让我们开始吧!

先来个小测验。

请问下图的问号里,可以填入什麽?

再来问大家两题是非题,提示:getNumChildren()可以帮助取得接下来一层有的物件。

答案在今天的文章当中!

前情提要一下

考虑到有些夥伴不一定会都看过前面的文章,就捞叨一点把基本步骤再次附上,如果会的夥伴可以直接跳到 Q1。

我们已经知道大致上,每一个 Google 文件都会有 Element (元件),且每一个 Element 都会有 Attribute (属性)。今天我们主要会介绍蓝色的 Element 的部分。

那我们把 Element 展开来看,里面有很多小的物件,这边抓出其中最常用的四种。分别是段落、照片、表格与清单。

那我们的目标就是透过读取、写入、更新与删除(对的,参照 CRUD 的 format)来带大家让是怎麽操作这些表格。这边就节录一本书中的「段落、照片、表格与清单」,来作为今天我们的范例。

那在开始前,先来个重要的概念——

物件间的亲子关系(Parent / Child)

我们确实可以透过 getImages()getTables() 来取得几个比较重要的物件们。当我们想进入更细节的物件取得时,像是细节到 Table 的哪一格时,就会需要知道他们之间的亲子关系,或是说上下层关系。

这边我们要介绍一个观念叫 Parent()Children(),基本上我们可以透过 getParent()getChildren() 来取得上下层关系的物件。

但,唯独 Document 的物件例外,当我们用 let doc = DocumentApp.getActiveDocument(); 取得 doc 物件时,它既没有上层的 parent(用 getParent() 会得到 null),更没办法直接取得 children()(没有 getChildren() 的方式)。

简单来说,就是你想看到 Document 下层。需要明明白白地指派是要 Header、Body 还是 Footer。

那另外一间值得注意的事是, Child_index 会是每个物件就会算一次。所以当我们看这位在第一页的三个表单时,他会出现的 ChildIndex 并不是 1, 2, 3。那会是什麽呢?

而是 1, 4, 7,为什麽呢?因为在我们表单的前面有着「换行」刚刚有提到,「每个物件就算一次」,那当然就包括段落(Paragraph),而昨天我们有提到,空白段落(换行)也是一个 Object。

而更重要的是,我们说 parentchild 可以帮我们来取得有上下层关系的物件。那 Table 下面也有下层的 TableRow, TableRow 则还有更下层的 TableCell。

好,那到目前为止的概念,应该是能够帮你回答最前面那两题的。这边也公布参考答案——

那 Parent 和 Child 的概念很重要,因为回到我们的目的,当我们要「部分」删除或更新时,它就会派上用场。也附上这段检查用的程序码——

function readChild(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let tables = doc_body.getTables();
  for (let i=0;i<tables.length;i++){
    Logger.log(doc_body.getChildIndex(tables[i]))
    Logger.log(tables[i].getNumChildren())
    Logger.log(tables[i].getChild(0).getChild(0))
  }
}

好,理解这边的概念後,再来进入实作罗!

Step 1 从 Document 中进入 GAS

那这次我们不会用 Google Sheet,而是直接用 Google Doc 进入。

一样第一次会有存取验证需要大家按一下。这边仍是借用一下 D2 的影片。

Step 2 设定好 getBody()

我们先用 getActiveDocument() 抓出正在绑定的文件;那假设我们都是针对主要内文(Body)的部分,所以我们先设定好 getbody()

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

完整的架构概念,可以参考 Google 的官方文件

因为更新有比较复杂的细节,我们就先来讲讲删除。

Q3. 如何删除 Google Docs 中的表格

删除之前基本上要先取得,所以先让我们取得表格。这边我加入一些假表格。

接着,我们要读取表格,并进行删除。

Step 3 读取要删除的部分

然後接下来要先用昨天的 read 来进行读取。

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

来确认一下跑起来如何——

好,看来有抓到范围。

Step 4-1 用 clear() 删除整张表单

如果要将整个文件全部删除,程序码很简单——

function clearDoc(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  doc_body.clear();
}

跑起来长这样——

如果是要衔接 Step 3,将表格删除,那要怎麽做?我们来示范只删掉後面两个。因为 Tables 回传会是三个 Table 的 Objects,理论上 Tables 是长这样 [Table obj 1, Table obj 2, Table obj 3] ,而因为是 Array 的形式,我们要取的条件是 table[i] 中 i > 0 的情况。。

function deleteTables(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let tables = doc_body.getTables();
  for (let i=0;i<tables.length;i++){
    if(i > 0){
      tables[i].clear()
	}
  }
}

跑起来长这样——

好,那这就是删除整个表格的做法。但如果有的时候,我们只是要针对部分内容进行删除,又要怎麽做?这边就要用我们最一开始的 Children 的概念了。

Step 4-1 删除部分的表单

假设我想删掉第一列,那该怎麽办?我们有提到,因为 table 下面是以 tableRow 作为下层物件,我们可以直接用 table.getChild 来进行删除。而因为 Table Row 之间不会有其他空白段落,所以可以直接指派特定的那列。

举例来说,我想将全部表格的「第ㄧ列」删除。

function deleteSecondRow(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let tables = doc_body.getTables();
  for (let i=0;i<tables.length;i++){
    tables[i].getChild(0).clear()
  }
}

跑起来长这样——

要注意的是,我对 tables[i]getChild(index) 中的 index 输入的是 0,因为这边不是 Google Sheet,要回程序语言的以 0 为基准点。

那如果,我想将右下角的 Cell 删除呢?

function deleteBottomRightCell(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let tables = doc_body.getTables();
  for (let i=0;i<tables.length;i++){
    tables[i].getChild(1).getChild(1).clear();
  }
}

跑起来长这样——

值得注意的是,现在 Google Doc 不支援非长方形的表格了,所以用 Clear() 只会清除到里面的值。换句话说,如果今天你不想删掉列,而是列里面的特定值的话,可以写成——

function deleteRowValue(){
  let doc_body = DocumentApp.getActiveDocument().getBody();
  let tables = doc_body.getTables();
  for (let i=0;i<tables.length;i++){
  	for (let j=0;j<tables.getChild(0).getNumChildren;i++){
  	tables[i].getChild(1).getChild(j).clear();
  }
}

好,那如果今天我想要将第二直栏的表格删掉,该怎麽办?以目前 Google Docs 的架构,我们会需要创造另外一个表单,这另外找时间写。那因为表单是最复杂的,其他相对清单、照片与段落,基本上都是透过 Clear() 就可以删除,了解完表单後,理论上其他的实作就不会那麽难。

好,那今天就到这边。

结论

好,那今天我们主要交代了 Parent/Child 和如何删除,篇幅关系,更新我们留到明天分享;如果还有问题,透过留言之外,也可以到 Facebook Group,想开很久这次铁人赛才真的开起来哈哈哈,欢迎来当 Founding Member。如果不想错过可以订阅按赞小铃铛(?),也欢迎留言跟我说你还想知道什麽做法/主题。我们明天见。


<<:  [Day 16] 针对网页的单元测试(二)

>>:  熟习-使用

[铁人12:Day 28] 「AI 的未来十年」摘要 4:混合式架构

要把那些技术混合在一起,才能达到我们的目标呢? 符号式操作 (Symbolic Operation)...

[Vue] 判断图片是否存在

在开发Vue专案时,时常会使用binding的技巧,用以动态变更参数的值, 如下 <div c...

Day 19:AWS 是什麽?30天从动漫/影视作品看AWS服务应用 -《尼尔:自动人形》

今年春天重版出来的《尼尔:人工生命 ver.1.22474487139...》第二代《尼尔:自动人形...

[30天 Vue学好学满 DAY13] 组件

组件 可重复使用的实例,有自己的名子,引入後当元素使用。 Data 透过 return 定义为函数 ...

Day23 - ListView

上次学的Spinner需要点选下拉钮,才显示项目 而ListView则是直接把所有项目列出来 两者的...