CSVファイルを読み込んで云々というのはありがちな処理ですが、C言語の開発に不慣れな技術者にダブルクォートの有無やエスケープまで考慮した項目分割処理を組ませると項目分割した結果を構造体にセットして一度に返そうとするケースが多々。他の言語の影響なのでしょうが、そんな汎用性のないオーバーフローが目に見えた作りにするのだけは止めておくれ…。
個々人のスキルが如実に滲み出るC言語だけに技術者が10人居れば10通りの実装があって当然ではありますが、こんなやり方もあるよということでサンプルをば。
/*
Copyright (c) 2014 buzzyvox.com
Licensed under the MIT License.
http://opensource.org/licenses/mit-license.php
*/
#include <string.h>
static char *olds;
char *__datatok (char *s, const char *delim)
{
char *p, *token;
int dq = 0;
if (s == NULL)
s = olds;
if (*s == '\0')
return NULL;
token = s;
while (*s) {
if ((p = strstr (s, delim)) == NULL) {
olds = strchr (s, '\0');
break;
}
while (s < p) {
if (*s == '"') { dq++; }
s++;
}
if (!dq || (dq && !(dq % 2))) {
*p = '\0';
olds = p + strlen (delim);
break;
}
s++;
}
s = token;
while (s = strchr (s, '"')) {
size_t len = strlen (s) -1;
memmove (s, s + 1, len);
s[len] = '\0';
s++;
}
return token;
}
/* CSV項目分割 */
char *csvtok (char *s)
{
return __datatok (s, ",");
}
/* TSV項目分割 */
char *tsvtok (char *s)
{
return __datatok (s, "\t");
}
標準関数strtokのcsv対応版といった感じ。注意点(破壊型特殊な・ネスト不可)やI/Oの基本は継承しているので使い方はstrtok関数と同様。
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[]) {
char *tp, readline[256];
FILE *fp;
if ((fp = fopen ("test.csv", "r")) == NULL) {
return EXIT_FAILURE;
}
while (fgets (readline, sizeof (readline), fp) != NULL) {
tp = csvtok (readline);
puts (tp);
while (tp != NULL) {
tp = csvtok (NULL);
if (tp != NULL) puts (tp);
}
puts ("********************");
}
fclose (fp);
return EXIT_SUCCESS;
}
世の中には「もはやCSVと呼ばないだろ…」と愚痴りたくなるほどトリッキーな方言を用いたCSVファイルがあったりするので、関数として切り出すならこのようにシンプルで汎用性を保った作りにしておくのが一番です。