Help us understand the problem. What is going on with this article?

ブラウザ上でCSVファイルをパースする

More than 3 years have passed since last update.

背景

CSVファイルのアップロード機能を考えます。
一番シンプルな方法は、formタグにinput type="file"タグを入れて、指定したファイルをPOSTします。

例えば、HTMLタグ/フォームタグ/ファイルの送信欄を作る - TAG index Webサイト です。

問題

CSVファイルのバリデーションにサーバーへの送信が必須です。
POST前にチェックできれば、ユーザが意図しないCSVファイルを指定した場合に、迅速なフィードバックを返せます。
便利なユーザーインタフェースが作れます。

また、CSVファイルの仕様は

  • エスケープ
  • 区切り文字
  • 改行文字
  • ファイルのエンコーディング

を考慮する必要があります。面倒です。
自分では、パーサーを書きたくありません。

解決策

npmパッケージとして公開されているCSVパーサーを利用します。
今回は、csv-parserを使って、ブラウザ上でCSVファイルをパースします。

csv-parserは

id,first_name,last_name,email,gender,ip_address
1,Paul,Hill,phill0@yahoo.com,Male,213.190.9.236

のようなCSVを

{
  "id": "1",
  "first_name": "Paul",
  "last_name": "Hill",
  "email": "phill0@yahoo.com",
  "gender": "Male",
  "ip_address": "213.190.9.236"
}

JavaScriptオブジェクトに変換します。

browserifyを使うと、npmパッケージをブラウザで動くJavaScriptに変換できます。

実装

ユーザが指定したファイルを取得

input type="file"は指定ファイルが変わるとchangeイベントを早出します。
inputfiles属性からFileオブジェクトが取得できます。

document.querySelector('input')
  .addEventListener('change', (e) => {
    const file = e.target.files[0] // ファイルが取れます。
  })

イベントオブジェクトのtarget属性には、input要素(イベントが発生した要素)が入っています。

ファイルの中身を読み込む

File APIのreadAsText関数を使って読み込みます。

readAsText関数は引数にFileオブジェクトを指定できます。

const reader = new FileReader
reader.readAsText(file)

このとき第二引数でエンコードを指定できます。
WindowsのExeclで作ったCSVファイルを読み込むときは'shift-jis'を指定すると、文字化けを回避できます。

ファイルの読み込みは非同期で行われます。読み込みが終わるとonloadハンドラーが呼ばれます。
読み込んだ結果はinputresultプロパティから取得できます。

const reader = new FileReader
reader.onload = (e) => {
  const content = e.target.result // ファイルの中身が取れます。
}

CSVのパース

csv-parserを使います。
csv-parserはStream APIを実装しています。

データ入力

入力データをwrite関数で渡します。

文字列かBufferオブジェクトが指定できます。
readAsTextの読み取り結果は文字列です。そのまま指定できます。

const csv = require('csv-parser')
const stream = csv()

stream.write(e.target.result)

readAsArrayBufferを利用してArrayBufferオブジェクトを指定した場合はBuffuerに変換する必要があります。

const csv = require('csv-parser')
const stream = csv()

stream.write(new Buffer(e.target.result))

データ取得

変換結果はdataイベントハンドラーで取得します。

const csv = require('csv-parser')
const stream = csv()

stream.on('data',(data) => {
  const object = data // objectにパースされた値が取得できます。
})

ソースコード

index.js

index.js
const csv = require('csv-parser')
const stream = csv()

stream.on('data', function(data) {
  console.log(data)
})

document.querySelector('input')
  .addEventListener('change', (e) => {
    const reader = new FileReader
    reader.onload = (e) => {
      stream.write(e.target.result)
    }

    if (e.target.files[0]) {
      const file = e.target.files[0]
      reader.readAsText(file)
    }
  })

index.html

index.html
<input type="file">
<script src="bundle.js" charset="utf-8"></script>

実行

npm init -y
npm i -D browserify csv-parser
node_modules/browserify/bin/cmd.js index.js bundle.js

index.htmlを開きます。

動作イメージ

スクリーンショット 2016-06-24 14.05.04.png

その他

テスト用のCSVファイルはMockaroo - Random Data Generator | CSV / JSON / SQL / Excelで作りました。

参考

ledsun
編集リクエスト、コメント大歓迎です。
luxiar
Ruby on Rails専門のWebアプリケーション開発に特化した町田の受託開発企業です
http://www.luxiar.com/index.html
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした