TSVフォーマット
TSVはRFCのようなドキュメントが用意されていないので、さらに実装依存になるのだが、CSVよりもシンプルなルールであり、用途もさらにシンプルなものが多い点から、ここでは次のような規則をベースに考えることにする。
- レコード:複数のフィールドから構成される行。フィールドは「タブ」で区切られる。レコード(行)の終わりはCRLF
- フィールド:データに「タブ」と「改行」を含めることはできない
ここでは、TSVのフィールドに改行を含めないことにする。改行を含めようにするとクォート処理が必要になり、TSVのシンプルという特徴が消えてしまうからだ(なお、CSVよりも実装依存のフォーマットなので、TSVでもデータに改行を含めることができるソフトウェアもある)。
CSVをTSVへ変換するための落としどころフォーマット
TSVのフィールドが改行を含まないので、CSVをTSVに変換する場合、CSVのフィールドが改行を含んでいても困ることになる。また、仕様上はCRLFが行の区切りデータになっているが、C言語を使うと改行コードはLFで扱われるので、CRLFを厳密に使おうとすると煩雑になる。
ということで、仕様と実装と現実をすり合わせて、次のようなフォーマットを落としどころフォーマットと考えることにする。
CSV
- レコード:複数のフィールドから構成される行。フィールドは「,」で区切られる。レコード(行)の終わりはLF
- フィールド:データに「カンマ」や「ダブルクォーテーション」を含む場合はダブルクォーテーションで囲む必要がある。データとしてのダブルクォーテーションは必ず「""」とする必要がある
TSV
- レコード:複数のフィールドから構成される行。フィールドは「タブ」で区切られる。レコード(行)の終わりはLF
- フィールド:データに「タブ」と「改行」を含めることはできない
上記のルールでは、CSVファイルが改行をデータとして含んでいる場合はうまく扱うことができないが、改行をデータとして含むCSVデータは問題になりやすいのだ。ここでは、改行をデータに含むCSVデータは扱わないという前提でいくことにしよう。
実装
説明が長くなってしまったので、今回はここで切り上げる。実装については先行して以下に掲載しておくので、興味がある方は読んでみてもらえればと思う。書き換えたMakefileも含め、詳しい説明は次回以降で行う。
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
int main(int argc, char *argv[]) {
char *csvdata, *tsvdata;
int csvdata_bytes, tsvdata_bytes;
csvdata = file2str(argv[1]);
csvdata_bytes = strlen(csvdata);
tsvdata_bytes = csvdata_bytes;
tsvdata = calloc(sizeof(char), tsvdata_bytes + 1);
csv2tsv(csvdata, csvdata_bytes, tsvdata, tsvdata_bytes);
printf("%s", tsvdata);
return 0;
}
main.h
int csv2tsv(const char *, int, char *, int);
char *file2str(const char *)
util_csv.c
#include <stdbool.h>
static bool record_outputed;
static char gettsvchar(const char);
int csv2tsv(const char *ibuf, int ibufsize, char *obuf, int obufsize) {
// When the target is empty, no processing is done.
if (0 == ibufsize)
return 0;
const char *p_i, *end_i;
char *p_o;
int tsv_len = 0;
p_i = ibuf;
end_i = &ibuf[ibufsize - 1];
p_o = obuf;
// Indicates the state during parsing.
typedef enum FIELD_STATUS {
FIELD_END,
IN_FIELD,
IN_QUOTED_FIELD
} record_status;
record_status rs = FIELD_END;
record_outputed = false;
while (1) {
if ('\n' == *p_i) {
if (!record_outputed) {
// nothing
}
rs = FIELD_END;
*p_o = gettsvchar('\n');
++p_o;
++tsv_len;
} else {
switch (rs) {
case FIELD_END:
if (',' == *p_i) {
// nothing
} else if ('"' == *p_i) {
rs = IN_QUOTED_FIELD;
} else {
rs = IN_FIELD;
*p_o = gettsvchar(*p_i);
++p_o;
++tsv_len;
}
break;
case IN_FIELD:
if (',' == *p_i) {
rs = FIELD_END;
} else {
*p_o = gettsvchar(*p_i);
++p_o;
++tsv_len;
}
break;
case IN_QUOTED_FIELD:
if ('"' == *p_i) {
if (p_i == end_i) {
rs = FIELD_END;
} else if (',' == *(p_i + 1)) {
rs = FIELD_END;
++p_i;
} else if ('"' == *(p_i + 1)) {
*p_o = gettsvchar(*p_i);
++p_o;
++tsv_len;
++p_i;
}
} else {
*p_o = gettsvchar(*p_i);
++p_o;
++tsv_len;
}
break;
}
switch (rs) {
case FIELD_END:
*p_o = '\t';
++p_o;
++tsv_len;
record_outputed = false;
break;
case IN_FIELD:
case IN_QUOTED_FIELD:
break;
}
}
if (p_i == end_i || tsv_len == obufsize)
break;
else
++p_i;
}
return tsv_len;
}
static char gettsvchar(const char c) {
record_outputed = true;
if ('\t' == c) {
return ' ';
} else {
return c;
}
}
util_file.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
char *file2str(const char *filepath) {
struct stat st;
int filesize, c;
char *buf, *p;
FILE *fp;
stat(filepath, &st);
filesize = st.st_size;
buf = calloc(sizeof(char), filesize + 1);
p = buf;
fp = fopen(filepath, "r");
for (int i = 0; i < filesize; i++) {
c = fgetc(fp);
if (EOF == c) {
break;
}
*p = (char)c;
++p;
}
return buf;