見出し画像

卒業文集作成のための Google ドキュメント 結合!

久しぶりに GAS の紹介です!!

今回は、卒業文集作成のための Google ドキュメント 結合と題して、特定の Google ドライブ のフォルダに入っている ドキュメント を結合する!といものです!

卒業文集などの原稿を Google クラスルーム & Google ドキュメント で回収して、手作業で結合していったりしていませんか…??😢

ということで、

Google ドライブ のフォルダを指定して、その中身を自動でまとめる GAS を作ってみました!

中学校時代の担任の先生が、これで悩んでたのを思い出しました😁笑

とりあえず、動画もあるので、そちらもご覧ください!

[0] Google スプレッドシート の準備

下記のリンクから スプレッドシート のコピーを作成してください!

[1] プログラムの解説

拡張機能 ▶ AppsScript をクリックすると、プログラムの中身を確認することができます!

プログラムはこんな感じです。
使い方は後ほど!!

まずは、重複チェック.js

function checkDuplicateIds() {
  // 現在アクティブなスプレッドシートを取得
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  // "ドキュメント結合シート"という名前のシートを取得
  var sheet = ss.getSheetByName("ドキュメント結合シート");
  
  // シートの列Aのデータを取得
  var columnA = sheet.getRange("A:A").getValues();
  
  // idをキーとして行番号を保持するオブジェクトを初期化
  var idMap = {};
  
  // 重複したidを持つ行番号のセットを保持する配列を初期化
  var duplicateSets = [];

  // 列Aの各セルに対してループを実行
  for (var i = 0; i < columnA.length; i++) {
    // i行目のidを取得
    var id = columnA[i][0];
    
    // idが存在する場合
    if (id) {
      // idMapにすでにidが存在する場合、対応する配列に行番号を追加
      if (idMap[id]) {
        idMap[id].push(i + 1);
      } else {
        // idMapにidが存在しない場合、新しい配列を作成し行番号を追加
        idMap[id] = [i + 1];
      }
    }
  }

  // idMapの各エントリに対してループを実行
  for (var id in idMap) {
    // idに対応する行番号の配列が2以上の場合、重複セットに追加
    if (idMap[id].length > 1) {
      duplicateSets.push(idMap[id]);
    }
  }

  // 重複がない場合、アラートを表示し関数を終了
  if (duplicateSets.length === 0) {
    SpreadsheetApp.getUi().alert("重複はありませんでした。ドキュメント結合を実行してください");
    return;
  }

  // 重複行をハイライトするための色の配列を定義
  var colors = ["yellow", "green", "blue", "orange", "red"];

  // 重複セットの各エントリに対してループを実行
  for (var i = 0; i < duplicateSets.length; i++) {
    // i番目の重複セットの行番号の配列を取得
    var rows = duplicateSets[i];
    
    // 色の配列から色を選択(ループ)
    var color = colors[i % colors.length];

    // 重複セットの各行に対してループを実行
    for (var j = 0; j < rows.length; j++) {
      // 行の背景色を設定
      sheet.getRange(rows[j], 1, 1, sheet.getLastColumn()).setBackground(color);
    }
  }
}

次に、main.js

// カスタムメニューを作成する関数
function onOpen() {
  var ui = SpreadsheetApp.getUi();  // スプレッドシートのUIを取得
  ui.createMenu('カスタムメニュー')  // カスタムメニューを作成
    .addItem('重複確認', 'checkDuplicateIds')  // '重複確認'という名前のメニューアイテムを追加し、'checkDuplicateIds'関数を関連付け
    .addItem('ドキュメントを結合', 'showPrompt')  // 'ドキュメントを結合'という名前のメニューアイテムを追加し、'showPrompt'関数を関連付け
    .addToUi();  // 作成したメニューをUIに追加
}

// プロンプトダイアログを表示し、ドキュメントの名前を入力してもらう関数
function showPrompt() {
  var ui = SpreadsheetApp.getUi();  // スプレッドシートのUIを取得

  var result = ui.prompt(
    '新しいドキュメントの名前',  // プロンプトのタイトル
    'ドキュメントの名前を入力してください:',  // プロンプトの説明文
    ui.ButtonSet.OK_CANCEL);  // プロンプトのボタンセット(OKとキャンセル)

  var button = result.getSelectedButton();  // ユーザーがクリックしたボタンを取得
  var text = result.getResponseText();  // ユーザーが入力したテキストを取得

  if (button === ui.Button.OK) {  // OKボタンがクリックされた場合
    concatenateDocuments(text);  // 入力されたドキュメント名でドキュメントを結合する関数を呼び出し
  }
}

// ドキュメントを結合する関数
function concatenateDocuments(docName) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();  // アクティブなスプレッドシートを取得
  var sheet = ss.getSheetByName("ドキュメント結合シート");  // 'ドキュメント結合シート'という名前のシートを取得

  var data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getValues();  // 指定範囲のデータを取得(2行目から最終行まで、1列目と2列目)

  var destFolder = DriveApp.getFolderById("");  // 指定されたIDのフォルダを取得(フォルダIDは""に設定)
  var templateDocId = "";  // テンプレートドキュメントのIDを設定(ドキュメントIDは""に設定)
  var template = DriveApp.getFileById(templateDocId);  // テンプレートドキュメントを取得

  var newDocFile = template.makeCopy(docName, destFolder);  // テンプレートをコピーし、新しいドキュメントを作成
  var newDocId = newDocFile.getId();  // 新しいドキュメントのIDを取得
  var newDoc = DocumentApp.openById(newDocId);  // 新しいドキュメントを開く
  var body = newDoc.getBody();  // 新しいドキュメントの本文を取得

  var docContents = [];  // 文書の内容を保存するための配列を初期化

  for (var i = 0; i < data.length; i++) {  // データの各行に対してループ
    var progress = Math.floor((i / data.length) * 100);  // 進捗を計算
    SpreadsheetApp.getActiveSpreadsheet().toast('進捗: ' + progress + '%', '結合中', 3);  // 進捗を表示

    if (data[i][1] === 'TRUE' || data[i][1] === true) continue;  // TRUEが設定されている行はスキップ

    var folderId = data[i][0];  // フォルダIDを取得
    var folder = DriveApp.getFolderById(folderId);  // フォルダを取得
    var files = folder.getFilesByType(MimeType.GOOGLE_DOCS);  // フォルダ内のGoogleドキュメントを取得

    while (files.hasNext()) {  // 各ファイルに対してループ
      var file = files.next();  // 次のファイルを取得
      var doc = DocumentApp.openById(file.getId());  // ファイルを開く
      var contentElements = doc.getBody().getParagraphs();  // ドキュメントの段落を取得
      var fileName = file.getName();  // ファイル名を取得
      var studentId = fileName.split("_")[0];  // ファイル名から学籍番号を抽出
      docContents.push({ id: studentId, contentElements: contentElements });  // docContents配列に学籍番号と内容を追加
    }

    sheet.getRange(i + 2, 2).setValue('TRUE');  // 処理済みの行にTRUEを設定
  }

  docContents.sort(function (a, b) {  // 学籍番号でdocContents配列をソート
    return a.id.localeCompare(b.id);
  });

  if (docContents.length > 0) {  // docContents配列に内容がある場合
    for (var i = 0; i < docContents.length; i++) {  // docContents配列の各エントリに対してループ
      var contentElements = docContents[i].contentElements;
      for (var j = 0; j < contentElements.length; j++) {  // 各段落に対してループ
        var sourceElement = contentElements[j];  // 段落を取得
        var destElement = body.appendParagraph(sourceElement.getText());  // 段落のテキストを新しいドキュメントに追加

        var fontSize = sourceElement.getFontSize();  // フォントサイズを取得
        var fontFamily = sourceElement.getFontFamily();  // フォントファミリーを取得
        var bold = sourceElement.isBold();  // 太字かどうかを取得
        var italic = sourceElement.isItalic();  // 斜体かどうかを取得
        var underline = sourceElement.isUnderline();  // 下線があるかどうかを取得
        var alignment = sourceElement.getAlignment();  // 段落の整列を取得

        // スタイル属性を適用
        if (fontSize) destElement.setFontSize(fontSize);
        if (fontFamily) destElement.setFontFamily(fontFamily);
        if (bold !== null) destElement.setBold(bold);
        if (italic !== null) destElement.setItalic(italic);
        if (underline !== null) destElement.setUnderline(underline);
        if (alignment) destElement.setAlignment(alignment);
      }
      
      if (i < docContents.length - 1) {  // 最後のエントリ以外の場合
        body.appendPageBreak();  // 改ページを追加
      }
    }
  }

  try {
    var firstParagraph = body.getParagraphs()[0];  // 最初の段落を取得
    if (firstParagraph.getText() === '') {  // 最初の段落が空の場合
      firstParagraph.removeFromParent();  // 最初の段落を削除
    }
  } catch (error) {
    // エラーが発生した場合は何もしない
  }

  SpreadsheetApp.getActiveSpreadsheet().toast('終わりました', 'Status', 5);  // 終了メッセージを表示
}

コメントが多いから、めっちゃ多く見える🤣笑

話逸れますけど、最近はまじで ChatGPT でプログラム組めちゃいますし、解説もしてくれるので、不明な点は聞いてみてください!

[2] プログラムの動きについて

まず、スプレッドシート に ドキュメント が保存されているフォルダの ID を入れます

例えば、Google Classroom で卒業文集などを課題で配布して回収すると、自動的にフォルダが作成されます!
そのフォルダID をA列に入れて行きます!

このようなURLからとってきます。

ここにID にあります!

https://drive.google.com/drive/u/1/folders/ここにID

ドキュメント が入っているフォルダのIDを集めてください!

↑にIDだけ入れていきましょう!

こんな感じですね!

次に、テンプレートの準備をしていきます!

このテンプレートってなんことかというと。

結合した ドキュメント に設定したいページ設定をしたドキュメントになります!

次のように、

文集作りとかって、左右の幅を決めたりしますよね。
これをテンプレートで設定しておき、その設定を継承して、結合していくことになります!

業者さんに送るときや、コピー機で印刷するときとかありますよね笑

このドキュメントのIDをプログラム内に入れます!

ドキュメント の場合は、

https://docs.google.com/document/d/ ここにあります! /edit

ID をコピーしたら、main.js の 35行目

  var templateDocId = "";  // テンプレートドキュメントのIDを設定(ドキュメントIDは""に設定)

の " " の間に入れてください!

次に、完成した ドキュメント を保存するフォルダを指定します。

こちらも任意の完成フォルダを作り、そのフォルダのIDをプログラム内に入れます!(フォルダIDはさっきやったので省略しますね)

main.js の 34行目になります!

  var destFolder = DriveApp.getFolderById("");  // 指定されたIDのフォルダを取得(フォルダIDは""に設定)

" " の間に入れてください!

ここまで終わったら保存しましょう!

プロジェクトを保存 💾 をクリック!

これで準備完了です!!

[3] 実行

まずは、権限の承認をいつものように行ってください!

重複チェックのところで実行してもらえば大丈夫です!
▶ 実行 をクリック!

これで権限の承認が完了です!

重複チェックから行いましょう!

権限の承認が終われば、スプレッドシート にメニューが追加されています!

重複確認をクリック!
そうすると、もし間違えて同じ フォルダID を入れてしまっていたら、色を付けて教えてくれます!

同じ生徒がたくさん入ってきたらまずいですからね笑

もし、重複するものがあれば、削除してから、ドキュメントを結合をクリック!

これで、ドキュメントが結合されて、指定した完成フォルダの中にドキュメントが入っているはずです!!!

是非試してみてください!!

なお、これはいろいろとカスタマイズの可能性もあると思うので、ぜひアイデアがあればコメントで教えてください😋

Twitter

Facebook

ポートフォリオ

YouTube チャンネル

いちばんやさしい Google Apps Script


何かと0から1を作るのは大変だと思います。学校はどこも似たような問題課題に対応していると思います。それなのに、先生って自分だけで頑張ろうとするんですよね。ボクの資料やnoteが1になって、学校ごとの現状に合わせてカスタムしていただければと思います‼️