【コピペ可】スプレッドシートからGoogleFormを自動生成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206742/60c6560e-73a1-5f39-df65-1e8ed86e2c38.png)


こんにちは，だるだるし です
会社の代表してたり音声作品投稿してたりします
お金がありません，，，金か仕事をください，，，

はい，ていうのはおいておいて

# Googleフォームの自動生成
## 目標
GoogleフォームをスプレッドシートとGASで自動生成する

## 前段
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206742/47241b10-adcf-6ccf-da30-68a127fce1a2.png)

『 [スタディステーション](https://service.nexsjp.com/studyStation/)』という（うちの会社の）サービス（現在完全無料なので本当に”サービス”）がある．

各自がGoogleフォームで作成したテスト，問題を皆で共有，検索できるサービス．

ここに小学二年生用の九九のサンプルを投稿したかったが，ポチポチ作るのはめんどくさかったので自動化したい．

[【GAS】スプレッドシートからテスト用のGoogleFormを自動生成する](https://qiita.com/aki_number16/items/e2ac30e7c4d2d44a02ad)

上記の記事を参照した結果，以下気になったので一応プログラマとして改良する

 - 速度が遅い（多分毎回シートにアクセスしてるせい）
 - 自由記述の解答形式に対応
 - 汎用性を持たせる（選択肢や正答の数を無制限にする，セルを決め打ち指定するのを避ける）
 - 冗長で複雑な表現を修正（配列をオブジェクトにするなど）
 - その他軽微な機能追加

## 答え
さっくり答え
九九作った時の「[スプレッドシート](https://docs.google.com/spreadsheets/d/1KfjAs6xeXq7XbO5EF_tfthT9R8KBREUp_40t2w0HIAM/edit?usp=sharing)」

で，Googleフォームを生成するGASのコード
正直変数名とかエラーハンドリングは適当にしてあるのでご了承

```javascript
//データをオブジェクト形式に変換
function getDataObjects(keyRow, dataRow) {
  let dataObjects = [];
  let keys = [];
  for (let i in keyRow) {
    let key = keyRow[i];
    if (key) {
      keys[key] = {
        key: key,
        i: i,
      };
    }
  }
  for (let row of dataRow) {
    let obj = {};
    for (let k in keys) {
      let key = keys[k];
      obj[key.key] = row[key.i];
    }
    dataObjects.push(obj);
  }
  return dataObjects;
}

//メイン関数
function myFunction() {
  //シート名
  let sheet = SpreadsheetApp.getActive().getSheetByName('シート1');

  let lastRow = sheet.getLastRow();//行
  // let lastColumn = sheet.getLastColumn();//列
  let formData = {
    title: "Googleフォーム",
    description: "",
    order: false,
  };

  //全データ取得
  let _sheet = sheet.getDataRange().getValues();
  let _formData = getDataObjects(_sheet[0], [_sheet[1]])[0];

  //フォーム名と説明文を設定
  formData.title = _formData.title || formData.title;
  formData.description = _formData.description || formData.description;
  formData.order = (_formData.order == 1);
  Logger.log(formData);

  //フォームを作成
  let form = FormApp.create(formData.title);
  form.setDescription(formData.description);
  form.setShuffleQuestions(formData.order);

  //クイズモードに設定
  form.setIsQuiz(true);

  //問題データ
  let questions = getDataObjects(_sheet[3], _sheet.splice(5, lastRow - 1));

  //問題文・選択肢を作成
  for (let question of questions) {
    let choices;
    //正解の数によってラジオボタンまたは、チェックボックスに形式を変える
    let item;
    switch (question.format) {
      case 1: //数値解答
        item = form.addTextItem();
        let textValidation = FormApp.createTextValidation()
          .setHelpText('数値で入力してください')
          .requireNumber()
          .build();
        item.setValidation(textValidation);
        break;

      case 2: //記述
        item = form.addTextItem();
        break;

      default: //選択
        let n = 1;
        switch (question.multiple) {
          case 1: //複数解答 //チェックボックスの作成
            item = form.addCheckboxItem();
            choices = [];
            while (question["choices" + n] || question["choices" + n] === 0) {
              let correct = false;
              for (let i in question.numOfCorrect) {
                if (question["choices" + n] === question["answer" + i]);
                correct = true;
                break;
              }
              let choice = item.createChoice(question["choices" + n], correct);//text,isCorrect(booblean)
              choices.push(choice);
              n++;
            }
            break;

          default: //単一解答 //ラジオボタンの作成
            item = form.addMultipleChoiceItem();
            //選択肢と解答の番号を取得
            choices = [];
            while (question["choices" + n] || question["choices" + n] === 0) {
              let choice = item.createChoice(question["choices" + n], question["choices" + n] === question.answer1);
              choices.push(choice);
              n++;
            }
            break;
        }
        break;
    }

    //問題文・選択肢・配点・必須をセット
    item.setTitle(question.statement || "問題");
    if (choices) {
      item.setChoices(choices);
    }
    item.setPoints(question.points);
    // item.setRequired(true);

    //フィードバックをセット
    // Logger.log(question.feedback);
    // Logger.log(question.format);
    if (question.format != 2 && question.format != 1) {
      if (question.feedback) {
        item.setFeedbackForCorrect(FormApp.createFeedback().setText(question.feedback).build());
      }
      if (question.feedbackForI) {
        item.setFeedbackForIncorrect(FormApp.createFeedback().setText(question.feedbackForI).build());
      }
    }
  }
}
```

## 取り敢えず使い方

### 元となるスプレッドシートを作成

見にくくて申し訳ないですが，以下のようにスプレッドシートを作成．
スプレッドシートの作成方法は別の記事等を参照のこと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206742/fe32159f-9253-08ba-39a3-55eda8e3a312.png)

#### ざっくり解説
一行目は全体に共通する「キー」
二行目は全体に共通する「値」
三行目は空（無視される）
四行目は問題に関する「キー」
五行目はキーの説明（無視される）
六行目以降は問題の「値」（一行で一問）

一行目の「title」の直下の値がGoogleフォームの「タイトル」，
一行目の「description」の直下の値がGoogleフォームの「概要」
になる

同様に
四行目の「statement」の列が問題の「問題文」の列
四行目の「format」の列が問題の「解答形式」の列

「キー」と「値」の列が対応していれば列の順序は自由
また，「キー」が空の列は無視されるので，計算やメモの列を確保可能

正解と選択肢はいくつでも登録可能
例えば選択肢は
「choices1」「choices2」「choices3」「choices4」「choices5」，，，と増やす
「choices1」から詰めて入力

### スクリプトエディタ起動
「ツール」→「スクリプト エディタ」
許可等求められた場合は適宜対応
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206742/2d8a3ff2-1e70-3c82-f352-3fbc56407977.png)

#### 上記スクリプトをコピペ，実行
「実行」を押して実行
ただし「デバッグ」の右隣が「myFunction」になっていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206742/9461a6fd-9113-4028-3c34-1def7ed4e39c.png)


#### 完成
「実行完了」が出たら完成
Googleドライブの”トップ”に出来ている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206742/93635f93-43af-8a61-6dcc-fe06f43febf3.png)

## ポイント
"シート1"のところはシート名に合わせる

```javascript
let sheet = SpreadsheetApp.getActive().getSheetByName('シート1');
```
  
最初にシート全体を読み込んで読み込み回数を減らす

```javascript
let _sheet = sheet.getDataRange().getValues();
```

「getDataObjects関数（自作）」で「question」オブジェクトにする
for文では「of」を使用して「qestionList[i][5]」のようなわかりにくい表現を避ける

```javascript
//問題データ
let questions = getDataObjects(_sheet[3], _sheet.splice(5, lastRow - 1));

//問題文・選択肢を作成
for (let question of questions) {
  ,,,
}
```

テキスト入力解答欄は [addTextItem](https://developers.google.com/apps-script/reference/forms/form#addTextItem()) で作成
解答形式を指定する場合は [createTextValidation](https://developers.google.com/apps-script/reference/forms/text-validation-builder) を使用
以下は解答を数字に限定する例

```javascript
item = form.addTextItem();
let textValidation = FormApp.createTextValidation()
  .setHelpText('数値で入力してください')
  .requireNumber()
  .build();
item.setValidation(textValidation);
```

問題順をランダムにするには [setShuffleQuestions](https://developers.google.com/apps-script/reference/forms/form#setShuffleQuestions(Boolean)) をtrueにする

```javascript
let form = FormApp.create("title");
form.setShuffleQuestions(true);
```


## 問題点  

 - 高速化したけど遅い（大量の問題を作る場合は結構待つ）
 - 自動生成では自由記述型の解答形式の問題に正答を設定できない（手動か選択型なら可能，[参考](https://issuetracker.google.com/issues/117437423?pli=1)）


## おわり

こんな感じです
お付き合いいただきありがとうございました
また，「スタディステーション」等についても記事にしていただきますので
今後ともよろしくお願いたします

あと，仕事ください