ブラウザ上で、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 変換は必須な気がします。

以上