LoginSignup
28
23

More than 5 years have passed since last update.

RFC4180準拠のCSV読み込みクラス

Last updated at Posted at 2016-03-18

boostなにそれ美味しいの?

美味しいに決まってんだろが!

なんで入れてねえんだよ! 勝手にライブラリ入れちゃ駄目? マジか、お前の会社車輪の再発明しまくりなのかよ、おいふざけんなよboostだぞ。Qiitaとかに落ちてるソースじゃねえんだぞ?

これが業務系では稀に良くある事なのよ。いや、マジで。そんなバカな、そんなトコねえよって思うだろうが、あるんだよそれが。そういう客もいるんだよ。笑ってやるから社名言ってみろって? 言えるわけねえだろ。

そんでまた今時CSVかよ

あんな曖昧な方言だらけのフォーマット誰も使ってねえよ。嘘だよ、俺らの業界って宇宙の時間軸がGoogleさんとかとなんかズレてっから全然未だに現役なんだよな。バリバリ使われてんだよな。

で、どういうCSVなんだよ? 先頭TABはコメントとか臭いの入ってねえだろうな?

え? 他の会社からの提供物だから分からない? そうかよ、お前いっつもなんも分かんねえよな!

じゃあRFC4180に準拠するから後は勝手にやれ

そういう闇深い何かのせいで、CSVパーサーを作る羽目になってしまった。さあRFC準拠か。どれどれ。

ふむふむ。なんてこった。

誰だよこのRFC書いた奴は。ダブルクォートで囲ったらCRLF入れられるとか、それ標準にすんなよ。実装の事考えてねえだろ。これ真面目に実装してる奴っているの? シンプルさがウリなんじゃないのかCSVは。そこスポイルしてどーすんだよ。

えーとまとめると、

  • 各レコードの区切りはCRLF
  • ファイル末尾のCRLFは省略できる
  • 先頭行のヘッダはあってもなくてもよい
  • デリミタはコンマ
  • ダブルクォートで囲んでも囲まなくてもよい
  • ダブルクォートを含める場合はダブルクォートの前にダブルクォートを付加してエスケープさせる
  • CRLFもコンマもダブルクォートで囲めばセーフ

こんなトコか。
先頭文字が「#」だったらコメントだと思ってたけど、RFC4180では定義されてないんだな。っつーことはあれも方言だったのかよ。いやー今まで知らなんだ。そして知らなくても良かった知識。

コメントとかあったら、も一つ面倒になるとこだったよ。
例えば、

aaa,"b
#bb","ccc",

これの2行目がコメントの様に見えて実は b[CRLF]#bb とかいうデータだもんな。

"""" 
こういう奴とかもあるんだな。

CSVのくせにちょっと複雑とかふざけんなよ、楽しくなってきたじゃねえかよ。

つまり単純にCRLFで分割させるのはNG

結局1文字ずつ見てくしかないって事じゃん。
ほんでstd::vector<std::vector<std::string>>に突っ込んでしまえばいいんだろ、分かったよ。

しかしここでそう単純には行かない。(事もないんだけど)
極東の島国に住む我々ときたら、厚切りジェイソンに「Why Japanese people!」と絶叫されるおかしな言語を操る種族。2バイト文字があるんだよ、そうなんだよ、考慮すべきだよね。
今回はShift-JISって事でね。だからShift-JISでインクリメントするわけなんですよ。
MSだけなら_mbsincでも使えばいいじゃん(いいじゃん)って話なんだけど、一応MS方言は使わない方向でね。

別に2バイト目が5Cでも問題にならんから、考慮しなくてもいいじゃねーかって?

その通りなんだけどな。ま、いいじゃん。折角だしさ。何が折角なんだ。

というわけで書いてみた

csv_parser.hpp
#pragma once

#include <string>
#include <vector>
#include <fstream>

class csv_parser
{
public:
    const std::vector<std::vector<std::string>> &rows() {
        return rows_;
    }

    template <typename Function>
    inline void read(const std::string &file_name, Function strinc)
    {
        std::ifstream f(file_name, std::ios::binary);

        std::string str((std::istreambuf_iterator<char>(f)),
            std::istreambuf_iterator<char>());

        std::string::const_iterator it = str.begin();

        auto it_begin = it;

        while (it != str.end())
        {
            if (*it == '"')
            {
                it_begin = ++it;

                for (; !is_double_quotation(it, str);)
                    strinc(it);

                str.erase(it);
            }

            for (;;)
            {
                if (it == str.end())
                {
                    push_field_with_row(it_begin, it);
                    break;
                }
                if (is_delimiter(it))
                {
                    push_field(it_begin, it);
                    increment_delimiter(it);
                    break;
                }
                if (is_crlf(it))
                {
                    push_field_with_row(it_begin, it);
                    increment_crlf(it);
                    break;
                }

                strinc(it);
            }

            it_begin = it;
        }
    }

    inline void read(const std::string &file_name) {
        read(file_name, [](std::string::const_iterator &it){ ++it; });
    }

private:
    std::vector<std::vector<std::string>> rows_;
    std::vector<std::string> fields_;

    void push_field_with_row(const std::string::const_iterator &_First, const std::string::const_iterator &_Last) {
        fields_.push_back(std::string(_First, _Last));
        rows_.push_back(fields_);
        fields_.erase(fields_.begin(), fields_.end());
    }

    void push_field(const std::string::const_iterator &_First, const std::string::const_iterator &_Last) {
        fields_.push_back(std::string(_First, _Last));
    }

    bool is_delimiter(const std::string::const_iterator &it) const {
        return (*it == ',');
    }

    bool is_crlf(const std::string::const_iterator &it) const {
        return ((*it == '\r') && (*(it + 1) == '\n'));
    }

    void increment_delimiter(std::string::const_iterator &it) const {
        ++it;
    }

    void increment_crlf(std::string::const_iterator &it) const {
        it += 2;
    }

    bool is_double_quotation(const std::string::const_iterator &it, std::string &str) const {
        if (*it == '"') {
            if ((it + 1) == str.end())
                return true;

            if (*(it + 1) != '"')
                return true;
            else
                str.erase(it);
        }
        return false;
    }
};

main関数がこれね。

main.cpp
#include "csv_parser.hpp"
#include <iostream>

int main(int argc, char* argv[])
{
    csv_parser parser;

    parser.read<>("test.csv", [](std::string::const_iterator &it) {
        unsigned char c = *it;
        it += (((c >= 0x81) && (c <= 0x9f)) || ((c >= 0xe0) && (c <= 0xfc))) ? 2 : 1; });

    for each(auto row in parser.rows())
    {
        std::cout << "-------" << std::endl;
        for each(auto r in row)
            std::cout << " * " << r << std::endl;
    }

    return 0;
}

一応readの第2引数でShift-JIS用インクリメントをしてんだけど、まあ別にいらなければいらないかも。

parser.read("test.csv");

これだけでも動くんだけどね。
いいのよ、隙あらばラムダって事ですよ。お前はホントにラムダが必要だったのかと小一時間問い詰められても仕方ないです。
template <typename Function>書きたかっただけちゃうんか、と。
そう言われたら返す言葉もございません。
そうさ、書きたかっただけさ。

まあ100行ちょいで

そこそこちゃんと動いてるのかな。EUCとかUTF8も大体いけるやろ(適当)
インクルードガードを#pragma onceで書いてるから気をつける人は気をつけてね。俺はVCとgccで通れば満足だからifndefとかしねーよ、へへへん。

makeいらずでhppだけincludeすれば使えるんで

お手軽かなーと。現実に使う場合は異常系とかしっかりさせないとダメなんだけど、取り敢えずこれでいいや。

そんでboost入れさせてない企業の人、これを見てたら速攻悔い改めるように。

28
23
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
23