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;
  • Visual Studio Codeで編集しているようす

    Visual Studio Codeで編集しているようす

参考