インプット情報（GoogleスプレッドシートかCSV）を HTML テーブルに変換する方法

## 概要

インプット情報（Google スプレッドシートか CSV）を HTML テーブルに変換し、Web ブラウザで視覚的に確認できる HTML ファイルを生成する方法を検討します。

1. Python スクリプト
2. VBA マクロ
3. シェルスクリプト
4. Google Apps Script

## 1. Python スクリプト

### コード

```python
import csv

def csv_to_html_with_colspan(input_file, output_file):
    with open(input_file, 'r', encoding='utf-8') as csv_file:
        reader = csv.reader(csv_file)
        with open(output_file, 'w', encoding='utf-8') as html_file:
            html_file.write("<!DOCTYPE html>\n")
            html_file.write("<html><head><title>CSV to HTML Table</title></head><body>\n")
            html_file.write("<table border='1'>\n")

            for row_index, row in enumerate(reader):
                html_file.write("<tr>\n")
                empty_count = 0
                for cell in row:
                    if cell == "":
                        empty_count += 1
                    else:
                        if empty_count > 0:
                            tag = "th" if row_index == 0 else "td"
                            html_file.write(f"<{tag} colspan='{empty_count}'></{tag}>\n")
                            empty_count = 0
                        tag = "th" if row_index == 0 else "td"
                        html_file.write(f"<{tag}>{cell}</{tag}>\n")
                if empty_count > 0:
                    tag = "th" if row_index == 0 else "td"
                    html_file.write(f"<{tag} colspan='{empty_count}'></{tag}>\n")
                html_file.write("</tr>\n")

            html_file.write("</table>\n")
            html_file.write("</body></html>\n")

    print(f"HTML file has been created: {output_file}")

# 使用例
input_file_path = "path/to/test.csv"  # 入力CSVファイルのパス
output_file_path = "path/to/output.html"  # 出力HTMLファイルのパス
csv_to_html_with_colspan(input_file_path, output_file_path)
```

### 実行方法

1. `input_file_path` と `output_file_path` を自分の環境に合わせて指定します。
2. スクリプトを実行すると、指定された HTML ファイルが生成されます。

---

## 2. VBA マクロ

### コード

```vba
Sub CsvToHtmlWithColspan()
    Dim csvFilePath As String
    Dim htmlFilePath As String
    Dim htmlFile As Object
    Dim fs As Object
    Dim line As String
    Dim isFirstLine As Boolean
    Dim columns() As String
    Dim emptyCount As Integer
    Dim i As Integer

    csvFilePath = "C:\path\to\test.csv"  ' 入力CSVファイルのパス
    htmlFilePath = "C:\path\to\output.html"  ' 出力HTMLファイルのパス

    Set fs = CreateObject("Scripting.FileSystemObject")
    Set htmlFile = fs.CreateTextFile(htmlFilePath, True)

    htmlFile.WriteLine "<!DOCTYPE html>"
    htmlFile.WriteLine "<html><head><title>CSV Table</title></head><body><table border='1'>"

    Open csvFilePath For Input As #1
    isFirstLine = True

    Do Until EOF(1)
        Line Input #1, line
        columns = Split(line, ",")
        htmlFile.WriteLine "<tr>"
        emptyCount = 0

        For i = LBound(columns) To UBound(columns)
            If columns(i) = "" Then
                emptyCount = emptyCount + 1
            Else
                If emptyCount > 0 Then
                    htmlFile.WriteLine "<" & IIf(isFirstLine, "th", "td") & " colspan='" & emptyCount & "'></" & IIf(isFirstLine, "th", "td") & ">"
                    emptyCount = 0
                End If
                htmlFile.WriteLine "<" & IIf(isFirstLine, "th", "td") & ">" & columns(i) & "</" & IIf(isFirstLine, "th", "td") & ">"
            End If
        Next i
        If emptyCount > 0 Then
            htmlFile.WriteLine "<" & IIf(isFirstLine, "th", "td") & " colspan='" & emptyCount & "'></" & IIf(isFirstLine, "th", "td") & ">"
        End If
        htmlFile.WriteLine "</tr>"

        isFirstLine = False
    Loop
    Close #1

    htmlFile.WriteLine "</table></body></html>"
    htmlFile.Close

    MsgBox "HTMLファイルが " & htmlFilePath & " に出力されました。"
End Sub
```

### 実行方法

1. Excel で VBA エディターを開き（`Alt + F11`）、新しいモジュールにコードを貼り付けます。
2. `csvFilePath` と `htmlFilePath` を自分の環境に合わせて変更し、マクロを実行します。

---

## 3. シェルスクリプト

### コード

```bash
#!/bin/bash

INPUT_FILE="/path/to/your/test.csv"  # CSVファイルのパス
OUTPUT_FILE="/path/to/your/output.html"  # 出力HTMLファイルのパス

{
  echo "<!DOCTYPE html>"
  echo "<html><head><title>CSV to HTML Table</title></head><body>"
  echo "<table border='1'>"

  while IFS= read -r line; do
    echo "<tr>"
    IFS=',' read -ra cols <<< "$line"
    empty_count=0
    for col in "${cols[@]}" ""; do
      if [[ -z "$col" ]]; then
        ((empty_count++))
      else
        if ((empty_count > 0)); then
          echo "<td colspan='$empty_count'></td>"
          empty_count=0
        fi
        echo "<td>$col</td>"
      fi
    done
    echo "</tr>"
  done < "$INPUT_FILE"

  echo "</table>"
  echo "</body></html>"
} > "$OUTPUT_FILE"

echo "HTMLファイルが $OUTPUT_FILE に出力されました。"
```

### 実行方法

1. スクリプトをファイルに保存し、実行権限を付与します。
   ```bash
   chmod +x csv_to_html.sh
   ```
2. スクリプトを実行します。
   ```bash
   ./csv_to_html.sh
   ```

## 4. Google Apps Script のメモ

- Google Apps Script

  ```javascript
  //CSVファイルを読んでHTMLを出力する
  function processCsvAndGenerateHtml() {
    const folderId = "フォルダID"; // ここにフォルダIDを指定
    const folder = DriveApp.getFolderById(folderId);
    const files = folder.getFilesByName("data.csv");
    if (!files.hasNext()) {
      Logger.log("data.csv ファイルが見つかりません");
      return;
    }

    const file = files.next();
    const content = file.getBlob().getDataAsString("UTF-8");
    const rows = content.split("\n").map((row) => row.split("\t"));

    let htmlTop = "";
    let htmlBottom = "";
    let tableContent = "";

    rows.forEach((row) => {
      const firstCell = row[0] ? row[0] : "";

      if (firstCell.startsWith("＃")) {
        if (firstCell.startsWith("＃■固定出力部top")) {
          htmlTop += row.slice(1).join("").replace(/\t/g, "") + "\n"; // タブを除去
        } else if (firstCell.startsWith("＃■固定出力部bottom")) {
          htmlBottom += row.slice(1).join("").replace(/\t/g, "") + "\n"; // タブを除去
        } else if (firstCell.startsWith("＃行データ・機能")) {
          tableContent += `            <tr>\n`;
          tableContent += `                <td colspan=3>${row[1].trim()}</td>\n`;
          tableContent += `            </tr>\n`;
        } else if (firstCell.startsWith("＃行データ・状態")) {
          tableContent += `            <tr>\n`;
          tableContent += `                <td>${row[1].trim()}</td>\n`;
          tableContent += `                <td>${row[2].trim()}</td>\n`;
          tableContent += `                <td class="toggle-col">${row[3].trim()}</td>\n`;
          tableContent += `            </tr>\n`;
        } else {
          return; // 上の処理指定行以外はスキップする。
        }
      }
    });

    const finalHtml = htmlTop + tableContent + htmlBottom;

    const outputFileName = "output.html";
    const outputFile = folder.createFile(
      outputFileName,
      finalHtml,
      MimeType.HTML
    );
    Logger.log(`HTMLファイルが生成されました: ${outputFile.getUrl()}`);
  }
  ```

  ```javascript
  //GWSドライブのスプレッドシートを読み込んでHTMLを出力する
  function processSpreadsheetAndGenerateHtml() {
    const spreadsheetId = "スプレッドシートID"; // ここにスプレッドシートIDを指定
    const sheetName = "シート1"; // シート名を指定
    const sheet =
      SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
    const rows = sheet.getDataRange().getValues();

    let htmlTop = "";
    let htmlBottom = "";
    let tableContent = "";

    rows.forEach((row) => {
      const firstCell = row[0] ? row[0].toString() : "";

      if (firstCell.startsWith("＃")) {
        if (firstCell.startsWith("＃■固定出力部top")) {
          htmlTop += row.slice(1).join("").replace(/\t/g, "") + "\n"; // タブを除去
        } else if (firstCell.startsWith("＃■固定出力部bottom")) {
          htmlBottom += row.slice(1).join("").replace(/\t/g, "") + "\n"; // タブを除去
        } else if (firstCell.startsWith("＃行データ・機能")) {
          tableContent += `            <tr>\n`;
          tableContent += `                <td colspan=3>${row[1].trim()}</td>\n`;
          tableContent += `            </tr>\n`;
        } else if (firstCell.startsWith("＃行データ・状態")) {
          tableContent += `            <tr>\n`;
          tableContent += `                <td>${row[1].trim()}</td>\n`;
          tableContent += `                <td>${row[2].trim()}</td>\n`;
          tableContent += `                <td class="toggle-col">${row[3].trim()}</td>\n`;
          tableContent += `            </tr>\n`;
        } else {
          return; // 上の処理指定行以外はスキップする。
        }
      }
    });

    const finalHtml = htmlTop + tableContent + htmlBottom;

    const folderId = "出力用フォルダID"; // 出力用フォルダID
    const folder = DriveApp.getFolderById(folderId);
    const outputFileName = "output.html";
    const outputFile = folder.createFile(
      outputFileName,
      finalHtml,
      MimeType.HTML
    );
    Logger.log(`HTMLファイルが生成されました: ${outputFile.getUrl()}`);
  }
  ```

  ```javascript
  //ログファイルを指定階層に出力する
  function outputLog(folder_id, msg) {
    // 実行日付をフォーマットする
    var date = new Date();
    var dateString = Utilities.formatDate(
      date,
      Session.getScriptTimeZone(),
      "yyyy-MM-dd"
    );
    var dateTimeString = Utilities.formatDate(
      date,
      Session.getScriptTimeZone(),
      "yyyy-MM-dd HH:mm:ss"
    );

    // ログの生成
    var log_message = dateTimeString + " " + msg;
    Logger.log(log_message); // Apps Scriptのログに記録
    var fileName = dateString + "_log.txt";

    // フォルダーIDを指定して取得する
    var folderId = folder_id; // ここに指定するフォルダIDを記入
    var folder = DriveApp.getFolderById(folderId); // 指定されたフォルダーを取得

    var files = folder.getFilesByName(fileName);
    var file;

    // ファイルが存在する場合、内容を追記
    if (files.hasNext()) {
      file = files.next();
      var oldContent = file.getBlob().getDataAsString();
      file.setContent(oldContent + "\n" + log_message);
    } else {
      // ファイルが存在しない場合、新しいファイルを作成
      file = folder.createFile(fileName, log_message);
    }
  }
  ```

  ```csv
  ＃メッセージ	現在の Service Status は異常です。
  ＃読み込み対象外
  ＃読み込み対象外	項目1	項目2	項目3
  ＃行データ・機能	機能A
  ＃行データ・状態	データ1	データ2	データ3
  ＃行データ・状態	データ4	データ5	データ6
  ＃行データ・機能	機能B
  ＃行データ・状態	データ7	データ8	データ9
  ＃行データ・状態	データ10	データ11	データ12
  ＃読み込み対象外
  ＃読み込み対象外
  ＃読み込み対象外
  ＃■固定出力部top	<!DOCTYPE html>
  ＃■固定出力部top	<html lang="ja">
  ＃■固定出力部top	<head>
  ＃■固定出力部top	    <meta charset="UTF-8">
  ＃■固定出力部top	    <title>テーブル列の表示・非表示</title>
  ＃■固定出力部top	    <style>
  ＃■固定出力部top	        .hidden {
  ＃■固定出力部top	            display: none;
  ＃■固定出力部top	        }
  ＃■固定出力部top	        .header {
  ＃■固定出力部top	            background-color: #0078d4;
  ＃■固定出力部top	            color: white;
  ＃■固定出力部top	            padding: 1rem;
  ＃■固定出力部top	            text-align: center;
  ＃■固定出力部top	        }
  ＃■固定出力部top	        .message {
  ＃■固定出力部top	            color: black;
  ＃■固定出力部top	            padding: 1rem;
  ＃■固定出力部top	            text-align: center;
  ＃■固定出力部top	        }
  ＃■固定出力部top	        .container {
  ＃■固定出力部top	            max-width: 900px;
  ＃■固定出力部top	            margin: 2rem auto;
  ＃■固定出力部top	            background-color: white;
  ＃■固定出力部top	            border-radius: 8px;
  ＃■固定出力部top	            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  ＃■固定出力部top	            padding: 1rem;
  ＃■固定出力部top	        }
  ＃■固定出力部top	    </style>
  ＃■固定出力部top	    <script>
  ＃■固定出力部top	        function toggleColumn() {
  ＃■固定出力部top	            var cols = document.querySelectorAll('.toggle-col');
  ＃■固定出力部top	            cols.forEach(function(col) {
  ＃■固定出力部top	                col.classList.toggle('hidden');
  ＃■固定出力部top	            });
  ＃■固定出力部top	        }
  ＃■固定出力部top	    </script>
  ＃■固定出力部top	</head>
  ＃■固定出力部top	<body>
  ＃■固定出力部top	    <div class="header">
  ＃■固定出力部top	        <h1>Service Status</h1>
  ＃■固定出力部top	    </div>
  ＃■固定出力部top	    <div class="message">
  ＃■固定出力部top	<h1>
  ＃■固定出力部top	現在の Service Status は異常です。
  ＃■固定出力部top	</h1>
  ＃■固定出力部top	    </div>
  ＃■固定出力部top	    <div class="container">
  ＃■固定出力部top	        <button onclick="toggleColumn()">列の表示/非表示</button>
  ＃■固定出力部top	        <table border="1">
  ＃■固定出力部top	            <tr>
  ＃■固定出力部top	                <th>項目1</th>
  ＃■固定出力部top	                <th>項目2</th>
  ＃■固定出力部top	                <th class="toggle-col">項目3</th>
  ＃■固定出力部top	            </tr>
  ＃■固定出力部bottom	        </table>
  ＃■固定出力部bottom	        <!-- 他のステータス項目を追加 -->
  ＃■固定出力部bottom	    </div>
  ＃■固定出力部bottom	</body>
  ＃■固定出力部bottom	</html>
  ```

## 5. Google Sheets でシートにボタンを配置して Google Apps Script を視覚的に実行する

ボタンを挿入する

- Google Sheets を開く

  - 「挿入」メニューをクリックし、「図形描画」を選択
  - 図形描画ツールで適当な図形（四角形など）を描画
  - 描画した図形を選択し、「保存して閉じる」をクリック

- 図形にスクリプトを割り当てる
  - シート上に挿入された図形を右クリックし、「スクリプトを割り当て」を選択
  - 表示されるダイアログボックスに実行したい関数名を入力（例えば、copyFiles など）。
  - 「OK」をクリック
  - これで、図形（ボタン）をクリックすると、指定した Google Apps Script が実行される