T.H/ 2023年 3月 31日/ 技術

はじめに

こんにちは。T.H.です。
今回は、Google DocsでのGASについてです。

経緯

プライベートで進行表を作成する機会がありました。進行表とはイベント等のスムーズな進行を目的とした台本のようなものです。以下のような条件を満たすことが多いドキュメント形式となります。

  • 主に表形式のドキュメントである
  • 一行あたりの情報量が多い
  • 多くの場合、紙に印刷する必要がある
  • 改版を重ねることが多い

進行表の例
進行表の例

過去に同様のドキュメントをSpread Sheetで作成したのですが

  • 紙に印刷する必要がある

の一点で非常に苦しんだ記憶があり、印刷に向き、かつ表形式で作成したいという要望のためGoogle Docsでの作成となりました。
また、改版の度に番号や、時刻の計算をしなおすのはこれまた非常に辛いため、表の番号や時刻を自動計算するマクロを埋め込んでしまおうと相成りました。

GASの埋め込み方法

簡単です。Google Docsのメニューより、「拡張機能」「Apps Script」で作成画面に移行します。

コード

コード全文を乗せます。プライベートのツールということもありかなり雑な作りになっています。

function onOpen() {
  var ui = DocumentApp.getUi();
  ui.createMenu('マクロ')
    .addItem('CutNo&時刻自動計算', 'myFunction')
    .addToUi();
}

// パラメータ
var startRowIndex = 2;//最初のCutのIndex
var cutNoCellIndex = 0;//CutNoのCell Index
var timeCellIndex = 1;//時刻のCell Index
var timeSpanCellIndex = 2;//所要時間のCell Index
var doc = DocumentApp.getActiveDocument();

function myFunction() {
  var concertDate = new Date('yyyy/mm/dd');//イベント当日

  var curTime = concertDate;
  num_time = setNumAndTimes(0, 1, curTime)
  num_time = setNumAndTimes(1, num_time[0], num_time[1])
  num_time = setNumAndTimes(2, num_time[0], num_time[1])

}

function setNumAndTimes(table_no, prevNo, curTime) {

  var table = doc.getBody().getTables()[table_no];
  var rowNum = table.getNumRows();
  var loopMax = rowNum - startRowIndex - 1 // 開始位置のオフセットを考慮

  for ( var i = 0;  i < loopMax;  i+=2 ) {
    var row = table.getRow(startRowIndex + i);
    if(i == 0){
      // 初回のCutNo
      var cutNoCell = row.getCell(cutNoCellIndex);
      cutNoCell.setText(prevNo);
      if (table_no == 0){
        // 1テーブル目の初回は開始時間を表から取得
        var timeCell = row.getCell(timeCellIndex);
        var timeText = timeCell.getText();
        var hhmm = timeText.split(':');
        var hour = parseInt(hhmm[0], 10);
        var minute = parseInt(hhmm[1], 10);
        curTime.setHours(hour);
        curTime.setMinutes(minute);
      }
      else{
        // 2テーブル目以降は前回の時刻をセット
        var timeStr = curTime.getHours().toString().padStart( 2, '0') + ':' + curTime.getMinutes().toString().padStart( 2, '0')
        var timeCell = row.getCell(timeCellIndex);        
        timeCell.setText(timeStr)
      }
    }

    //加算する時間
    var spanCell = row.getCell(timeSpanCellIndex);
    var spanText = spanCell.getText();

    var mmss = spanText.split(':');
    var minute = parseInt(mmss[0], 10);//経過時間はmm:ss
    var second = parseInt(mmss[1], 10);
    //時間の加算処理
    curTime.setMinutes(curTime.getMinutes() + minute);
    curTime.setSeconds(curTime.getSeconds() + second);

    // 次の時刻
    if (i == loopMax - 1){
      // 最後の1回は次の部ために保存
      lastNo = Math.floor((i + 2) / 2) + prevNo
      lastTime = curTime
    }
    else
    {
      // 時刻を次のCutNoの行にセット
      var nextTimeStr = curTime.getHours().toString().padStart( 2, '0') + ':' + curTime.getMinutes().toString().padStart( 2, '0')
      var nextRow = table.getRow(startRowIndex + i + 2);
      var nextTimeCell = nextRow.getCell(timeCellIndex);
      nextTimeCell.setText(nextTimeStr)

      // 次のCutNoをセット(時刻計算とループの都合上、CutNoも次のセルを入力する)
      var nextCutNoCell = nextRow.getCell(cutNoCellIndex);
      nextCutNoCell.setText(Math.floor((i + 2) / 2) + prevNo);
    }
  }
  return [lastNo, lastTime]
}

解説

下記のようにonOpen()関数内でcreateMenu()を実行することで、Docsのメニューに項目を追加することが出来ます

function onOpen() {
  var ui = DocumentApp.getUi();
  ui.createMenu('マクロ')
    .addItem('CutNo&時刻自動計算', 'myFunction')
    .addToUi();
}

setNumAndTimes()関数で表の一番左の列に番号を埋め、2番目の列に時刻を入れていきます。3番目の列は経過時刻で2番目の列の時刻計算に使用します。setNumAndTimes()が3回呼ばれているのはドキュメント上の3つのテーブルに処理を行うことを想定しているためです。

個人的に面白いと思ったのは下記の
doc.getBody().getTables()関数で、ドキュメント内のテーブルのリストを取得できる点です。ドキュメント内のテーブルのみを選択して取得することで現実のドキュメントとイメージの齟齬を少なく直感的に操作できます。たったこれだけの工夫ですが、使う側としては印象が変わるものですね。

function setNumAndTimes(table_no, prevNo, curTime) {

  var table = doc.getBody().getTables()[table_no];

適用する表の形式が、2行を結合して見た目上一つの列として取り扱う必要があるため、ループも2行で1つとなるように少し変則的になっています。

  for ( var i = 0;  i < loopMax;  i+=2 ) {
    var row = table.getRow(startRowIndex + i);

やっていることは前述のとおりシンプルなのですが、Spread Sheetのようにセルの表示フォーマットを細かく指定するような機能は無いため、経過時間は毎度文字列をパースして計算する必要があります。

    //加算する時間
    var spanCell = row.getCell(timeSpanCellIndex);
    var spanText = spanCell.getText();

    var mmss = spanText.split(':');
    var minute = parseInt(mmss[0], 10);//経過時間はmm:ss
    var second = parseInt(mmss[1], 10);

最後に

誰しもExcel系列の印刷には苦しめられた経験があると思いますので、表形式であっても思い切ってWord系列のソフトを使ってみるのもありかなと思いました。Google Docsならマクロもjava scriptで書けますのでかなりとっつきやすいと思います。

ちなみに、今回のコーディングにあたり真っ先にChat-GPTに聞いてみたところ、Spread Sheetのメソッド群を使用したコードを紹介され少し困惑しましたが、続けて質問しなおしたところまともな返答が返ってきました。100%の答えを求めるには辛いですが、きっかけをつかむのにはやはり優秀ですね。

About T.H

North Torch株式会社 プログラマ 技術的な経歴は.NETアプリケーションが一番長い。 その他はまだまだ勉強中。