D23 - 如何用 Apps Script 自动化地创造与客制 Google Slides?(四)一次抓出所有简报中的「特定文字」与备注

今天的目标:

要怎麽快速搜集在 Slides 中出现的特定文字,并在 Sheet 上标示其出现的页码?今天的结果预计是这样——

今天的主题情在在於,现代沟通很常会用 PPT。很多时候会需要来回传 PPT,而为了传达说「这一页我想改什麽」,我们会使用「注解」如下图(以 D21 生成的文为例,在拿给主管後收到了回馈),大大的被写上了「距离太远」。

传达上的另一种方式,是「另外开一个新的文字方块」来沟通,容易看到被大大的贴上了「主标换行」。

那当我们在「检查简报时」会有个重复的行为,就是每次要改简报,我们就要从头到尾的简报都看过是不是还有「待办事项」,有时还是难免漏掉一些小部分。有没有什麽方式,可以帮助我们自动地搜集所有的 Comment 或标示上「TO-DO」的文字方块,并整理成 Google Sheet 呢?而对应的关键问题是——

  1. 要怎麽一次性的搜集在 Google Slides 中出现的特定文字/注解,并在 Google Sheet 上标示出其页码?

好,那就让我们开始吧!


Q1 要怎麽一次性的搜集在 Google Slides 中出现的特定文字/注解,并在 Google Sheet 上标示出其页码?

输入 Input

  • Google Sheet 作为 GAS 的入口
  • 一份已经在沟通的 Google Slides 作为目标
    • 上面写上的沟通项目、要改的部分,都有在最前面写上 TO-DO

输出 Output

  • Google Sheet 上的一个表格,标示着「Slides 页码」与「待办事项文字内容」

预计会变成这样——

Step 1 从 Google Sheet 进入 GAS 并设定指定资料夹 ID

今天我们用 Google Sheet 作为连结 GAS 的管道,让我们借用 D14 的影片。

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

接着,我们抓出要核对的 Presentation ID,这边我们用 D21 的「自动化爲铁人赛的每一篇贴文生封面图」 的结果 Slides 为例。抓出 ID 的示范如下——

并将这 ID 到我们的 GAS 当中设定为一个参数。

var target_slide_ID = "your_pres_ID_here"

有夥伴反应这边不确定怎麽做,就一起准备了影片——

接着,就是读取简报了,直接作为 Step 2

Step 2 用 Slide ID 读取投影片们

这边我们就用 SlidesApp.openById() 搭配 getSlides() 来取得所有的投影片。接着再用一个 for 回圈 Print 出所有档案们。

function listAllTodos(){
  // READ SLIDES
  let pres = SlidesApp.openById(target_slide_ID);
  let slides = pres.getSlides();
  Logger.log("The number of slides are "+ slides.length)
  for(let i = 0; i<slides.length; i++){
  	Logger.log("Read Slides No."+ i)
  }
}

跑起来长这样——

Step 3 读取投影片中的「特定文字」们

这次我们要读的特定文字是「To-Do」,逻辑是,搜寻每一张投影片的元素(Page Elements)确认,如果是属於含有文字方块的(Shape),那就确认他呈现的文字中,前五个是不是 "TO-DO",是的话就抓出来并写上页数。程序码结合前面步骤就会变成——

function listAllTodos(){
  // READ SLIDES
  let pres = SlidesApp.openById(target_slide_ID);
  let slides = pres.getSlides();

  // LIST TARGET TEXT and page
  for(let i = 0; i<slides.length; i++){
    let slide = slides[i]
    let page_elements = slide.getPageElements();
    for(page_element of page_elements){
      if(page_element.getPageElementType() == SlidesApp.PageElementType.SHAPE){
        let text = page_element.asShape().getText().asRenderedString();
        if (text.slice(0,5)=="TO-DO"){
          Logger.log("Slide No."+i+ " has a to-do:"+ text)
        }
      }
    }
  }
}

让我们来看看会跑怎麽样——

OK,那却时有读到我们在 Slide 第 12 与 13 页中的 TO-DO 以及其文字。接着就要写入 Google Sheet 中了。

Step 4 写入 Google Sheet 中

出现我们的老朋友 writeData() 拉!程序码让大家复习——

function writeData(data){
    let sheet = SpreadsheetApp.getActiveSheet();
    let starting_row = 2;
    let starting_col = 1;
    let num_row = data.length;
    let num_col = data[0].length;
    let range = sheet.getRange(starting_row, starting_col,  num_row, num_col);
    range.setValues(data);
}

再结合并改写我们的前面的部分

function listAndRecordAllTodos(){
  // READ SLIDES
  let pres = SlidesApp.openById(target_slide_ID);
  let slides = pres.getSlides();
  let result=[];
  let update_time = new Date().toLocaleDateString();

  // LIST TARGET TEXT and page
  for(let i = 0; i<slides.length; i++){
    let slide = slides[i]
    let page_elements = slide.getPageElements();
    for(page_element of page_elements){
      if(page_element.getPageElementType() == SlidesApp.PageElementType.SHAPE){
        let text = page_element.asShape().getText().asRenderedString();
        let starting_text = text.slice(0,5)
        if (starting_text=="TO-DO"){
          Logger.log("Slide No."+(i+1)+ " has a to-do:"+ text);
          result.push([update_time, i+1, text]);
        }
      }
    }
  }
  writeData(result)
}

让我们来看看会跑怎麽样——

好,那看起来有写入成功!但其实我们还有件事没做,就是「读取备注」(Read Comment)

Step 5 读取备注 Read Comment

会把「读取备注」分开写,是因为它相对比较复杂。目前查到能「读备注」的 Google 功能并不是现有的,而是旧版 API 中的 Drive.comment 换句话说,要另外设置。设置方式如下——

好,当我们设置完後,我们就可以取得 Comment 了!以下是我们这次 Comment 的资料——

那不罗唆,直接上程序码——

function readComments(){
  let comments = Drive.Comments.list(target_slide_ID)
  for(comment of comments.items){
    Logger.log("Comment Content is : "+ comment.content)
    Logger.log("Comment Anchor is : "+comment.anchor)
	Logger.log("Comment Anchor page is : "+comment.anchor['page'])
    Logger.log("Comment Anchor page is : "+JSON.parse(comment.anchor)['page'])
  }
}

这段程序码执行起来长这样——

而输出的图放大在这边——

可以核对到 comment.content 读到的发现确实是我们要的 Comment 没错。但为什麽要对 comment.anchor 另外再取 JSON 呢?这边说明一下。comment.anchor 的意思是指,「这个 Comment 的锚⚓️」,也就是位在哪张 Slide 当中。但因为它原本 API 会让我们取得这样的资讯——

Logger.log(comment.anchor)
// {"type":"shape","uid":1632396166038,"page":"SLIDES_API791698242_104","targets":["SLIDES_API791698242_105"]}

Logger.log(comment.anchor['page'])
// null

明明是 Dict,却不让我们用 Dictionary 的取法,这是因为它现在在物件中这一整串不被理解为 Dictionary。需要另外再透过 JSON.parse() 把它变成我们可以读的 Dict。所以才会用这样的读法——

Logger.log(JSON.parse(comment.anchor)['page'])
// SLIDES_API791698242_96

所以最後我们能读到 Comment 和所在位置了,要怎麽结合起来?这边我们使用 {} ,也就是 Dictionary,它的好处是「提取」与「确认是否存在」上使用的时间相对其他资料结构检疫。但因为 Googel Apps Script 通常是小量做,所以相对时间与空间复杂度不用想太多。但对 Dict 不熟者可以参考 MDN 的介绍

function readAndWriteCommentsWithDict(){
  let comments = Drive.Comments.list(target_slide_ID)
  let page_id_content={}
  let update_time = new Date().toLocaleDateString();
  for(comment of comments.items){
    let page_content = comment.content;
    let page_id = JSON.parse(comment.anchor)['page']
    if ((page_id in page_id_content)){
      page_id_content[page_id].push(page_content)
    }else{
      page_id_content[page_id] = [page_content]
    }
  }

  let pres = SlidesApp.openById(target_slide_ID);
  let slides = pres.getSlides();
  let result = [];
  for(let i = 0; i<slides.length; i++){
    let slide = slides[i];
    let slide_id = slide.getObjectId();
    if (slide_id in page_id_content){
      result.push([update_time, i+1, page_id_content[slide_id]])
    }
  }
  Logger.log(result)
}

跑起来长这样——

好,那今天就是我们的结果了。

好,那今天就到这边。今天我们主要学了:

  1. 如何读取 Google Slides 中的特定文字与所在页面
  2. 如何读取 Google Slides 中的 Comments
  3. 如何将上述两者写到 Google Sheet 中

注意的是,今天第 2 点的「读 Comment」其实是 Google Drive 中的档案都可以用,但细节的程序码会需要再修正就是。


今天进入了 Slide 的最後一天,希望对大家有所帮助。如果还有问题,透过留言之外,也可以到 Facebook Group,想开很久这次铁人赛才真的开起来,欢迎来当 Founding Member。如果不想错过可以订阅按赞小铃铛(?),也欢迎留言跟我说你还想知道什麽做法/主题。我们明天见。


<<:  LeetCode解题 Day23

>>:  [ Day8 ] Web 小回归

Day11 远端共同协作 - 使用 GitHub

大家好我是乌木白,今天来向大家介绍GitHub,我自己很喜欢的一个可以做很多功能的网站!! 什麽是...

DAY 7 - 棘刺壳章鱼

大家好 今天也来涂鸦献丑一下~ 本日想尝试一下阴影跟反光 今天目标是画一只类似章鱼但是却有拥有带刺壳...

[Day25] 第二十五章-新增空白的point表单 (跨资料查询还有对应细节)

前言 昨天我们完成了point简单的read 跟route model controll等 今天我们...

前端工程学习日记第12天

使用绝对定位 / 相对定位 设计版型 https://ithelp.ithome.com.tw/up...

Day22_控制项(A17营运持续管理之资讯安全层面)-2021/10/05

▉A.17 Information Security Aspects Of Business营运持续...