notebook

都内でWEB系エンジニアやってます。

GoogleSlideで設定したリンクのリンク先ページ番号を自動更新する

Googleスライドで、スライド内リンクを生成すると

対象スライドへの参照が保持され、編集などでページ数が変わったとしても対象スライドへのリンクが保たれた状態になる

ExportでPDFにした場合でもPDF内でリンクが有効な状態で資料作成の際には便利である

今回は、目次用途で作成したページのリンク先のページ番号を記載し、GAS実行時に目次のページ番号を最新の状態に更新したい

ページ番号をわざわざ入れる必要ないだろって話はあるが、できるのか? というのが気になってしまったのでやってみた

こういうやつ((p7)(p8)などの部分)

最終的なコード

いきなりだが次のような感じで実現できた

function updateTocLinkPageNumber() {
  const presentation = SlidesApp.getActivePresentation()

  const slides = presentation.getSlides()

  // 目次スライドの指定
  const tocSlide = slides[2]
  // TEXT_BOXのShapeを取得
  const shapes = tocSlide.getShapes().filter(s => s.getShapeType() === SlidesApp.ShapeType.TEXT_BOX)

  shapes.forEach(shape => {
    // リンクが貼られているテキスト行を取得
    const textRange = shape.getText()
    const paragraphs = textRange.getParagraphs().filter(p => {
      const links = p.getRange().getLinks()
      
      if (links.length === 0) {
        return false
      }

      return links[0].getTextStyle().getLink().getLinkType() === SlidesApp.LinkType.SLIDE_ID
    })
    
    // 各リンクの行ごとにページ番号の取得と更新
    paragraphs.forEach(paragraph => {
      const textRange = paragraph.getRange()
      const link = textRange.getLinks()[0].getTextStyle().getLink()
      const slideId = link.getSlideId()
      const pageIndex = slides.findIndex(s => s.getObjectId() === slideId) + 1
      const slide = slides.find(s => s.getObjectId() === slideId)
          
      const text = textRange.asString()
      const pageNumPattern = / \(p\d+\)/
      const match = text.match(pageNumPattern)
      
      if (match){
        textRange.replaceAllText(match[0], '')
      }

      const pageNumText = ` (p${pageIndex})`
      const textLength = paragraph.getRange().asString().length
      // -1しないと改行文字列が含まれてしまうため改行後に注釈が含まれてしまう
      textRange.insertText(textLength - 1, pageNumText)
      textRange.getTextStyle().setLinkSlide(slide)
    })
  })
}

流れとしては次のような流れ

  • 目次のスライドの指定
  • TEXT_BOXのShapeを取得
  • リンクが貼られているテキスト行を取得
  • 各リンクの行ごとにページ番号の取得と更新

目次のスライドの指定

サンプルでは固定

TEXT_BOXのShapeを取得

  • プレースホルダなど、リストなどを記述するときの大枠
  • テキストが入っている図形という扱いらしい
  • 複数行のテキストが入っている

枠線の中

一般的なスライドだとタイトルのテキストボックスとページ番号のテキストボックスも含まれる

リンクが貼られているテキスト行を取得

  • textRange.getParagraphs()で行ごとのテキストが取得可能
  • getLinks()でリンク情報を取得
  • リンクのタイプがSLIDE_IDのものを取得
    • スライド内リンク
    • 今回は行全体にリンクが設定しているという前提にしている(links[0]で決め打ち)

各リンクの行ごとにページ番号の取得と更新

ドキュメントは下記

Class TextRange  |  Apps Script  |  Google for Developers

Class Link  |  Apps Script  |  Google for Developers

  • ページ番号の特定

    • リンク先のスライドIDを取得
      • link.getSlideId()
    • スライドのIDを取得
      • slide.getObjectId()
    • 突合しリンク対象のスライドのインデックスを取得
  • ページ番号のパターンのテキストがすでにあれば一度削除する

    • - hoge (p10) のようなフォーマットで固定
  • 新たに最新の状態から取得したページ番号をリンク行の最後に挿入

    • insertText()
    • コメントの通り、テキストの長さから-1しているのは改行との間に入れたいため
  • リンクを貼り直す

    • setLinkSlide(slide)
    • リンクが設定されているtextRangeの内容を変更するとリンクが解かれてしまうため再度リンクの設定が必要

最初行のテキスト自体を再生成しリンクしようとしたが、うまくいかずreplaceでページ番号の注釈部分を削除、追加しリンクを張り直すというような処理にした

考え方的にどうしても副作用を伴わない処理でやろうとしてしまうのでメンタルモデルが違っていて、なかなか厳しかった

  • ページ内リンクした後のリンクの内容

一度スライド内リンクを編集で見てみると#slide=idを指定していて、この値がURLにも反映されている

まとめ

  • GASを用いてGoogleSlideのページ内リンクのリンク先ページ番号を最新状態に更新する目次用のスクリプトを作成した
  • スライド上でどのようにテキストを扱っているかの理解につながった
    • 今後スライド操作を行う機会があれば考え方は把握できたのでスムーズにできそう