付録:csv2tsvソースコードほか

tasks.json

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Clang",
            "type": "process",
            "command": "make",
            "args": [
                "build"
            ],
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "label": "Test",
            "type": "process",
            "command": "make",
            "args": [
                "test"
            ],
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

Makefile

CMD=    csv2tsv.exe

SRCS=   $(wildcard *.c)
OBJS=   $(SRCS:.c=.o)

CC= clang
CFLAGS+=-g
EXIST=  cmd.exe //C if exist

build: $(CMD)

$(CMD): $(OBJS)
    $(CC) $(CFLAGS) -o $(CMD) $(OBJS)

.c.o:
    $(CC) -c $< -o $@

test: test-kyua

test-kyua: $(CMD)
    cd tests; kyua test

test-original: $(CMD) 
    pwsh .\tests\test.ps1

report: $(CMD) clean-report
    cd tests; kyua report-html

clean: clean-report
    $(EXIST) $(CMD) del $(CMD)
    $(EXIST) main.o del $(OBJS)
    $(EXIST) $(CMD:.exe=.ilk) del $(CMD:.exe=.ilk)
    $(EXIST) $(CMD:.exe=.pdb) del $(CMD:.exe=.pdb)
    $(EXIST) nul del *.tmp
    $(EXIST) csv2tsv.exe.stackdump del csv2tsv.exe.stackdump

clean-report:
    $(EXIST) .\tests\html rmdir .\tests\html //S //Q

main.h

int csv2tsv(const char *, int, char *, int);
char *file2str(const char *)

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(tsvdata_bytes + 1, sizeof(char));

  csv2tsv(csvdata, csvdata_bytes, tsvdata, tsvdata_bytes);

  printf("%s", tsvdata);

  return 0;
}

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 - Windows APIを使うように書き換えたバージョン

#include <stdio.h>
#include <windows.h>

char *file2str(const char *filepath) {
  /*
   * 指定されたファイルを開く
   */
  HANDLE hFile;

  // ファイルを開く
  hFile = CreateFile(filepath, // ファイル名
    GENERIC_READ,              // 読み込みのために開く
    0,                         // 共有しない
    NULL,                      // デフォルトセキュリティ
    OPEN_EXISTING,             // 既存のファイルを開く
    FILE_ATTRIBUTE_NORMAL,     // 通常のファイル
    NULL);                     // 属性およびフラグなし

  // ファイルが開けなかった場合の処理
  if (INVALID_HANDLE_VALUE == hFile) {
    fprintf(stderr, "no such file: %s\n", filepath);
    exit(EXIT_FAILURE);
  }

  /*
   * ファイルサイズを取得し、ファイルサイズ+1のメモリを確保
   */
  DWORD dwFileSize;
  dwFileSize = GetFileSize(hFile, NULL);

  // ヒープを生成
  HANDLE hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0, 0);

  // ヒープからメモリを確保
  char *buf, *p;
  int buflen = dwFileSize + 1;
  buf = (char*)HeapAlloc(hHeap, 
    HEAP_ZERO_MEMORY, 
    sizeof(char) * buflen);
  p = buf;

  /*
   * ファイルの中身をメモリへ読み込み
   */
  DWORD dwBytesRead;
  while (ReadFile(hFile, p, buflen, &dwBytesRead, NULL) 
    && dwBytesRead > 0) {
    p += dwBytesRead;
  }

  /*
   * ファイルを閉じる
   */
  CloseHandle(hFile);

  /*
   * ヒープを閉じる
   */
  CloseHandle(hHeap);

  return buf;
}

参考