ブラウザ上で、CSVファイルをSJISに変換し、動的生成してダウンロードする処理を書いてみる。

業務管理システム等で、SJISのCSVをアップロードしたりダウンロードをすることはよくあると思います。

その度に悩ましいのが文字コード変換。

サーバにPOSTして変換するのも手ですが、全てブラウザ内で完結させることも出来ます。

手順

  1. input[type="file"]でCSVファイルを読み込む。
  2. 読み込んだCSVファイルの文字コードをSJISに変換。
  3. 新たにCSVファイルを動的に生成しダウンロード。

1. input[type="file"]でCSVファイルを読み込む。

まず input[type="file"] でファイルが選択された時のイベントを設定します。

ここで File API の FileReader を使用してファイルを読み込みます。

FileReader でファイルを読み込む際にどんな形式で解釈するのか。それによって使うメソッドが違うのですが、今回は ArrayBuffer にして欲しいため、 readAsArrayBuffer メソッドを使用します。

readAsArrayBuffer が成功した場合は onload 、失敗した場合は onerror のイベントがそれぞれ実行され結果が渡されます。

// <input type="file" id="input">を指定
const inputElement = document.getElementById('input');
// CSVのMIME TYPE
const types = [
  'text/csv',
  'text/comma-separated-values',
  'text/plain',
  'application/csv',
  'application/excel',
  'application/vnd.ms-excel',
  'application/vnd.msexcel',
  'text/anytext'
];

/**
 * [ファイル読込処理]
 */
function input() {
  const reader = new FileReader();
  const file = inputElement.files[0];

  if (!file) {
    // fileが無いときは何もしない
    return;
  } else if (!((types.includes(file.type)) && /\.csv$/.test(file.name))) {
    // csvでは無いときはアラートを表示
    alert('CSVファイルではありません。');

    return;
  } else {
    // csvの時だけファイル読み込み開始
    reader.onerror = onerrorHandler;
    reader.onload = onloadHandler;
    reader.readAsArrayBuffer(file);

    return;
  }
}
inputElement.addEventListener('change', input);

2. 読み込んだCSVファイルの文字コードをSJISに変換。

前項の onload, onerror でそれぞれのイベントオブジェクトが渡されてきます。

エラーの場合、ファイルがなぜ読み込めなかったのか、evt.target.error.code にエラーコードが格納されているので、アラートを表示するようにしました。

成功した場合には、evt.target.result に ArrayBufferのデータが格納されています。

この ArrayBuffer のデータを Uint8Array で TypedArray (8ビット符号なし整数値の配列)に変換し、 encoding.js を利用して、SJISの文字コード配列に変換します。

encoding.detect でアップロードされた文字コードを判定し、from to で 指定した文字コードから文字コードへ変換することができます。

encoding.codeToString のメソッドを利用すると、文字コード配列を文字列に変換することが出来るので、 UNICODE や UTF8 の文字列に変換すれば、そこから JSON オブジェクトに変換して操作することも可能です。

const encoding = require('encoding-japanese');
const codeArray = undefined;

/**
 * [ファイル読込失敗処理]
 * @param  {Object} evt [イベント]
 */
function onerrorHandler(evt) {
  const err = evt.target.error;
  if (err.code && err.code === err.NOT_FOUND_ERR) {
    alert('ファイルが見つかりませんでした。');
  }
  else if (err.code && err.code === err.SECURITY_ERR) {
    alert('セキュリティのためファイルにアクセス出来ませんでした。');
  }
  else if (err.code && err.code === err.NOT_READABLE_ERR) {
    alert('ファイルが読み込めませんでした。');
  }
  else if (err.code && err.code === err.ABORT_ERR) {
    alert('ファイルの読み込みがキャンセルされました。');
  }
  else {
    alert('ファイルの読み取りエラーが発生しました。');
  }

  return;
}

/**
 * [ファイル読込成功処理]
 * @param  {Object} evt [イベント]
 */
function onloadHandler(evt) {
  // 8ビット符号なし整数値の配列に変換
  const uint8Array = new Uint8Array(evt.target.result);

  // 文字コードを判定
  const detected = encoding.detect(uint8Array);

  // 判定した文字コードからSJISへ変換
  codeArray = encoding.convert(uint8Array, {
    from: detected,
    to: 'SJIS'
  });

  // codeToStringで文字列にすることも可能
  // const str = encoding.codeToString(codeArray);

  return;
}

3. 新たにCSVファイルを動的に生成しダウンロード。

SJISに変換したファイルを File API の Blob を使用して、 blob オブジェクトを作り、 window.URL.createObjectURL でダウンロード用URLを生成します。

生成したURLを見えない aタグに設定し、clickイベントを実行させることで、SJISに変換されたファイルがダウンロードされます。

上記の方法はIEでは使えないので window.navigator.msSaveBlob を使用します。

またSafariはa要素のdownload属性に対応していないので、target属性に '_blank' を指定して別タブで開くまでしか出来ません。

const browser = new (require('ua-parser-js'))().getBrowser();

// <input type="button" id="output">を指定
const outputElement = document.getElementById('output');
window.URL = window.URL || window.webkitURL;

/**
 * [ファイルダウンロード処理]
 */
function output() {
  // 8ビット符号なし整数値の配列に変換
  const uint8Array = new Uint8Array(codeArray);
  // MIMEType指定
  const mimeType = 'text/css';
  // ファイル名指定
  const fileName = 'output.csv';
  // blob作成
  const blob = new Blob([uint8Array], {type: mimeType});

  // ダウンロード
  if (window.navigator.msSaveBlob) {
    // IE
    window.navigator.msSaveBlob(blob, fileName);
  } else if (window.URL || window.webkitURL) {
    // Chrome Safari FireFox
    window.URL = window.URL || window.webkitURL;
    const url = window.URL.createObjectURL(blob);

    const a = document.createElement('a');
    a.href = url;
    a.download = fileName;

    // Safari < 10.1 or 10.2 の場合は _blank必須
    if (browser.name === 'Safari' && (+browser.majar < 10 || ['10.0','10.0.1','10.0.2'].includes(browser.version))) {
      a.target = '_blank';
    }

    a.click();
  } else {
    // noop
  }

  return;
}

outputElement.addEventListener('click', output);

まとめ

  1. input[type="file"]でCSVファイルを読み込む。
  2. 読み込んだCSVファイルの文字コードをSJISに変換。
  3. 新たにCSVファイルを動的に生成しダウンロード。

SJISでCSVダウンロード出来るようになると、Excelでそのまま開くことが出来るようになるので、使い所はたくさんあると思います。

ちなみにわざわざSJISに変換しなくても、BOM付きUTF8でダウンロードすればExcelでも文字化けせずに開くことが出来ますが、アップロード時に文字コードを確認しなければならないことが多いので、SJIS変換は必須な気がします。

以上

HTML5JavaScript