汎用的な CSV リーダーを作ってみた

いやまあ、もうこんなの巷にいくらでも溢れてるだろうけど。


こいつの特徴は、どのヘッダにも依存しないってこと。 iterator_traits のために を使いました。まあでも とかそういうバイナリに影響するものじゃないし、これぐらいならいいよね?
テンプレート引数をうまく指定してやれば何でも出来る!でも逆に言えば単体では使い物にならないっていう。
やろうと思えば UTF-8 のデータを読んで UTF-16 の文字列を構築するとかもできるはず。
それぞれのテンプレート引数には何が必要なのかとかは、ソース見て判断してくだしぃ。


参考:中の技術日誌 わんくまライブラリ Wankuma.IO.CSVReaderクラス Version1


[2010/5/27 2:45 更新]

  • 入力をInputIteratorにしました。なんかC++っぽくなった気がする。
  • この CSV リーダ自体をイテレータっぽく扱えるようにしてみました。
    • ただ row::begin() を呼び出すたびに read_column() するので、reader3 の例みたいなへんてこな動きをします。
    • basic_csv_reader にカラム1個分先読みしたのを保持させればいいんでしょうけど、最初の例みたいに使える以上そんなオーバーヘッドを付けるのは嫌なので、これは仕方ないかなぁという気も。


[2010/6/4 2:17 更新]

  • 出力をOutputIteratorにしました。
  • CSV リーダ自体をイテレータっぽくしてもやっぱり何か微妙な感じなので削除
#include <iterator>

template<class InputIterator, class SpecialCharTraits>
class basic_csv_reader
{
public:
    typedef InputIterator input_iterator;
    typedef typename std::iterator_traits<InputIterator>::value_type char_type;
    typedef SpecialCharTraits sp_traits;

private:
    InputIterator it_;
    InputIterator end_;
    char_type next_;
    bool eol_; // end of line

    char_type next_char()
    {
        char_type c(next_);
        if (++it_ != end_) next_ = *it_;
        return c;
    }

    template<class OutputIterator>
    void construct_quote(OutputIterator& out)
    {
        while (!eof())
        {
            char_type c = next_char();
            if (c == sp_traits::quote)
            {
                if (!eof() && next_ == sp_traits::quote)
                {
                    next_char();
                    *out++ = c;
                }
                else
                {
                    return;
                }
            }
            else
            {
                *out++ = c;
            }
        }
    }

    template<class OutputIterator>
    bool construct_one_token(OutputIterator& out, char_type c)
    {
        if (c == sp_traits::quote)
        {
            construct_quote(out);
        }
        else if (c == sp_traits::delim)
        {
            return true;
        }
        else if (c == sp_traits::cr)
        {
            if (!eof() && next_ == sp_traits::lf)
            {
                eol_ = true;
                next_char();
            }
            return true;
        }
        else if (c == sp_traits::lf)
        {
            return true;
        }
        else
        {
            *out++ = c;
        }
        return false;
    }

    struct null_out : std::iterator<std::output_iterator_tag, char_type>
    {
        null_out() : dummy() { }
        reference operator*() { return dummy; }
        null_out& operator++() { return *this; }
        null_out operator++(int) { return *this; }
    private:
        value_type dummy;
    };

public:
    basic_csv_reader(InputIterator first, InputIterator last)
        : it_(first), end_(last), eol_(false)
    {
        if (eof()) eol_ = true;
        else next_ = *it_;
    }

    bool eof() const { return it_ == end_; }
    bool eol() const { return eol_; }

    bool next_row()
    {
        while (!eol()) read_column(null_out());

        if (!eof()) eol_ = false;

        return !eof();
    }

    template<class OutputIterator>
    OutputIterator read_column(OutputIterator out)
    {
        if (eol()) out;

        while (!eof())
        {
            char_type c = next_char();
            if (construct_one_token(out, c))
            {
                if (eof()) eol_ = true;
                return out;
            }
        }

        eol_ = true;
        return out;
    }
};
// こっからテスト
#include <string>
#include <iostream>
 
// 特別扱いする文字
struct special_char_traits
{
    static const char quote = '\"';
    static const char cr = '\r';
    static const char lf = '\n';
    static const char delim = ',';
};
 
typedef basic_csv_reader<const char*, special_char_traits> csv_reader;
 
int main()
{
    // a,"b""b","c,c"
    // d,e,f
    const char input[] = "a,\"b\"\"b\",\"c,c\"\r\nd,e,f";
    const char* ptr = input;
    csv_reader reader(input, input + sizeof(input) - 1);
    while (!reader.eof())
    {
        while (!reader.eol())
        {
            std::string str;
            reader.read_column(std::back_inserter(str));
            std::cout << str << '|';
        }
        std::cout << std::endl;
        reader.next_row();
    }
}
a|b"b|c,c|
d|e|f|