開発情報・ナレッジ

投稿者: ShiningStar株式会社 2025年9月4日 (木)

CSVファイルをページ上から一括アップロードするサンプルプログラム

ブラウザ上でCSVファイルを解析し、SPIRALのデータベースに一括登録するサンプルプログラムを紹介します。

注意点

付属のJavaScript CSVパーサーは簡易的なものです。複雑なCSV(改行を含む値など)には対応できない場合があります。
登録先のDB(サンプルでは`csvUpload`)と、CSVの列構成を事前に一致させておく必要があります。
CSVのヘッダー行にはフィールドの差し替えキーワードを入れる必要があります。

実装の概要

このサンプルは、単一のPHPファイル内でクライアント処理とサーバー処理を完結させています。

1. ユーザーがファイル選択フォームでCSVファイルを選ぶ。
2. JavaScriptがファイルを読み込み、ヘッダー行とデータ行に分解してJSON文字列に変換する。
3. 変換したJSONを、フォーム内のhiddenフィールドにセットする。
4. ユーザーが「アップロード」ボタンを押すと、フォームが同じページにPOST送信される。
5. サーバーサイドのPHPがPOSTされたJSONデータを受け取る。
6.
$SPIRAL->doBulkInsert()
を使用して、データをDBに一括登録する。
7. 処理結果のメッセージを画面に表示する。

サンプルコード

以下がサンプルコードです。この内容でページを作成してください。

<?//<!-- SMP_DYNAMIC_PAGE DISPLAY_ERRORS=ON NAME=XXX -->?>

<?php
// 処理結果メッセージ用
$message = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['columns_json'], $_POST['data_json'])) {
    $columns = $_POST['columns_json'];
    $data = $_POST['data_json'];

    if (!is_array($columns) || !is_array($data)) {
        $message = 'データ形式が不正です';
    } else {
        try {
            $db = $SPIRAL->getDataBase("csvUpload");

            $chunks = array_chunk($data, 1000);
            foreach ($chunks as $chunk) {
                $db->doBulkInsert($columns, $chunk);
            }

            $message = "アップロードと登録に成功しました";
        } catch (Exception $e) {
            $message = "エラー:" . $e->getMessage();
        }
    }
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>CSVアップロード</title>
</head>
<body>
<?php if ($message): ?>
  <p><?php echo htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); ?></p>
<?php else: ?>
  <form id="csvForm" method="POST">
    <input type="file" id="csvFile" accept=".csv" required>
    <input type="hidden" name="columns_json" id="columnsJson">
    <input type="hidden" name="data_json" id="dataJson">
    <button type="submit">CSVをアップロード</button>
  </form>

<script>
(function () {
  'use strict';

  // DOM構築完了後に初期化
  document.addEventListener('DOMContentLoaded', init, { once: true });

  function init() {
    try {
      // 必須要素の存在チェック
      const input       = document.getElementById('csvFile');
      const columnsEl   = document.getElementById('columnsJson');
      const dataEl      = document.getElementById('dataJson');

      // どれか一つでも欠けていれば何もしない
      if (!input || !columnsEl || !dataEl) return;

      // ファイル選択時のみ動作
      input.addEventListener('change', onFileChange);
    } catch (e) {
      // ここで例外を握りつぶすことで、他画面への影響を最小化
      console.error('[csv script] init error:', e);
    }
  }

  async function onFileChange(ev) {
    const file = ev.target && ev.target.files ? ev.target.files[0] : null;
    if (!file) return;

    try {
      const text = await readFileAsText(file, 'UTF-8');
      const parsed = parseCSV(text);

      if (!Array.isArray(parsed) || parsed.length < 2) {
        alert('データ行が不足しています(ヘッダー+1行以上が必要)');
        return;
      }

      const columns = parsed[0];
      const data = parsed
        .slice(1)
        // 空行・全要素空文字の行は除外
        .filter(row => Array.isArray(row) && row.some(v => String(v ?? '') !== ''));

      // 必須IDを再取得(別DOMへの参照切れ対策)
      const columnsEl = document.getElementById('columnsJson');
      const dataEl    = document.getElementById('dataJson');
      if (!columnsEl || !dataEl) return; // 念のため

      columnsEl.value = JSON.stringify(columns);
      dataEl.value    = JSON.stringify(data);
    } catch (e) {
      console.error('[csv script] parse error:', e);
      alert('CSVの読み込みまたは解析でエラーが発生しました。ファイルの形式を確認してください。');
    }
  }

  /**
   * File をテキストとして読む(BOM考慮)
   */
  function readFileAsText(file, encoding = 'UTF-8') {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onerror = () => reject(reader.error);
      reader.onload = () => {
        let result = reader.result;
        // UTF-8 BOM除去
        if (typeof result === 'string' && result.charCodeAt(0) === 0xFEFF) {
          result = result.slice(1);
        }
        resolve(result);
      };
      reader.readAsText(file, encoding);
    });
  }

  /**
   * シンプルCSVパーサ(ダブルクオート対応、CRLF対応)
   */
  function parseCSV(text) {
    const rows = [];
    let row = [];
    let value = '';
    let inQuotes = false;

    for (let i = 0; i < text.length; i++) {
      const char = text[i];
      const next = text[i + 1];

      if (inQuotes) {
        if (char === '"' && next === '"') {
          value += '"';
          i++;
        } else if (char === '"') {
          inQuotes = false;
        } else {
          value += char;
        }
      } else {
        if (char === '"') {
          inQuotes = true;
        } else if (char === ',') {
          row.push(value);
          value = '';
        } else if (char === '\n' || char === '\r') {
          // 改行(CRLF対応)
          if (char === '\r' && next === '\n') i++;
          row.push(value);
          value = '';
          rows.push(row);
          row = [];
        } else {
          value += char;
        }
      }
    }

    // 最終セル・最終行を反映
    if (value !== '' || row.length > 0) {
      row.push(value);
      rows.push(row);
    }

    return rows;
  }
})();
</script>

<?php endif; ?>
</body>
</html>

            

実行結果

ページにアクセスすると、ファイル選択ボタンが表示されます。
CSVファイルを選択して「アップロード」ボタンをクリックするとページがリロードされ、PHP処理の結果(成功またはエラーメッセージ)が表示されます。

まとめ

本記事では、JavaScriptでCSVを解析し、PHPでSPIRALのDBに登録する一連の流れを解説しました。
実際に活用される際はCSVファイルの中身に応じて適切なパースロジックを実装する必要があります。

解決しない場合はこちら コンテンツに関しての
要望はこちら