読者です 読者をやめる 読者になる 読者になる

syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

C++正規表現ライブラリ(Regex)でCSV解析

C++ プログラミングTips

(これは2011-01-30に書いた故OCNブログの記事を移植したものです)

C++11 (C++0x TR1) には正規表現ライブラリとして <regex> が存在するんですが、こいつを使って簡単なCSV文字列解析処理を書いてみました。参考にしたのは下記。
CSV形式のファイルをDataTableや配列等として取得する: .NET Tips: C#, VB.NET

解析用の末尾コンマをあらかじめ追加しておくこと、そして正規表現のみで解決しようとせず、C++によるループを利用して正規表現を繰り返し適用しているのがコツです。
一応Visual C++ 2008 SP1 / 2010以上の環境を想定していますが、g++などでもコンパイルできるように修正するのはそんなに難しくないはずです。ちなみにC++11のRaw string literalsが使えるコンパイラであれば、複雑な正規表現も記述しやすくなります。

#include <regex>
#include <vector>
#include <tchar.h>
#include <windows.h>
#include <atlbase.h>
#include <conio.h>

typedef std::basic_string<TCHAR> tstring;

bool ParseCsvLine(LPCTSTR pCsvLine, std::vector<tstring>& outArray)
{
    if (!pCsvLine)
    {
        return false;
    }

    try
    {
        const std::tr1::basic_regex<TCHAR> re(_T("\\s*(\"(?:[^\"]|\"\")*\"|[^,]*)\\s*,"));

        std::tr1::match_results<tstring::const_iterator> results;

        tstring strLine(pCsvLine);
        if (!strLine.empty() && strLine[strLine.length() - 1] != _T(','))
        {
            // 解析用末尾コンマを強制追加する。
            strLine += _T(',');
        }
        tstring::const_iterator start = strLine.begin();
        tstring::const_iterator end = strLine.end();
        while (std::tr1::regex_search(start, end, results, re))
        {
            outArray.push_back(tstring(results.str(1)));
            start = results[0].second;
        }

        // 解析の結果、たとえマッチした要素がひとつも無くても true を返す。
        return true;
    }
    catch (const std::exception& err)
    {
        err; // Warning C4101 対策。
        ATLTRACE("Exception[%s] : %s\n", typeid(err).name(), err.what());
        return false;
    }
}

void main()
{
    _tsetlocale(LC_ALL, _T(""));

    LPCTSTR pTargetCsvText = _T("foo, foo,long long,123.4,\"bar\",日本語, ,\" \",\",\",\"(x, y, z)\",,ソース");
    std::vector<tstring> csvColumnsArray;

    _tprintf(_T("Target CSV Text = \"%s\"\n"), pTargetCsvText);
    ParseCsvLine(pTargetCsvText, csvColumnsArray);
    printf("CSV Columns Count = %Iu\n", csvColumnsArray.size());
    for (size_t i = 0; i < csvColumnsArray.size(); i++)
    {
        const tstring& str = csvColumnsArray[i];
        _tprintf(_T("[%02Iu] = \"%s\"\n"), i, str.c_str());
    }
    puts("Press any...");
    _getch();
}