テキストファイルをcsv形式に書き換えるプログラミングを教えてください。


toshi.txtを対応する表として書き換えてtoshi.csvにする方法。

[code]

    |TOKYO|155|1|　　　　　TOKYO,155,1,
    |NAGOYA|108|3|　　→   NAGOYA,108,3,
    
[/code]

このように書き換えを行うプログラミングを教えてください。  
"|"を","に書き換えればいいのは分かるのですがうまくいきません。  
プログラミングは全くの初心者なのでよろしくお願いします。

[code]

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    int main()
    {
        char line[100];   // ファイルから読み込んだ行を格納用
        FILE *in_file, *out_file; // ファイルポインタ格納用
        int i;
    
        printf("Change a text file toshi.txt to csv file. \n");
    
        in_file = fopen("toshi.txt", "r"); 
        if (in_file == NULL) {
            printf("fopen_in error");
            return 0;
        }
        out_file = fopen("toshi.csv", "w"); 
        if (out_file == NULL) {
            printf("fopen_out error");
            return 0;
        }
    
        while (fgets(line, 100, in_file) != NULL){
            i = 1;
            while (line[i] != '\n'){
                switch (line[i]){ 
                case'|':
                    line[i]=',';
                    break;
                    if (line[i] != '|'){ line[i] = line[i]; }
                }
    
                i++;
            }
            fputc(line[i], out_file);
        }
    
        fclose(in_file);
        fclose(out_file);
        return 0;
    }
    
[/code]




# 前置き

**switch-case文のbreakについて:**

[code]

    case'|':
        line[i]=',';
        break;
        if (line[i] != '|'){ line[i] = line[i]; }
    
[/code]

caseに書かれているbreakは「このswitch文から抜ける」ことを意味します。  
つまりbreak文の後に書かれているif文は、絶対に実行されません。  
この様に絶対に実行されることがない文を`デッドコード (dead code)`と呼んだりします。

**文末かどうかの判断:**

[code]

    while (line[i] != '\n'){
    
[/code]

この処理で文末(改行位置)までループするという処理を行っていますが、toshi.txtの最後の行の後に`\n`(改行)が無かった場合にループが止まらない可能性があります。

[code]

    // 最後の行に改行があるケース
    |TOKYO|155|1|↓
    |NAGOYA|108|3|↓
    [EOF]
    
    // 最後の行に改行がないケース
    |TOKYO|155|1|↓
    |NAGOYA|108|3|[EOF]
    
[/code]

※[EOF]:end of file、ファイルの終端

以下の様に文字列終端を表すNULL文字(`'\0'`)かどうかもチェックしましょう。

[code]

    // 文字が改行('\n')でもNULL文字('\0')でもない間、ループする
    while (line[i] != '\n' && line[i] != '\0') {
    
[/code]

# 本題

**'|'から','への置換処理について:**

変換後のCSVの終端に','(以下カンマ)を付けない処理について考えます。

[code]

    |TOKYO|155|1| → TOKYO,155,1
    
[/code]

行頭の`|`は、ループを1から開始することで無視できます。  
行末の`|`は、次の文字が`\n`か`\0`の時に何も出力しなければ良さそうです。  
(次の文字(`line[i+1]`)が、`\n`か`\0`以外の場合にカンマへ置き換える)

[code]

    while (fgets(line, sizeof line, in_file) != NULL) {
        i = 1;
    
        // 文字が改行('\n')でもNULL文字('\0')でもない間、ループする
        while (line[i] != '\n' && line[i] != '\0') {
            switch (line[i]) {
            case '|':
                // '|'の時、次の文字(i+1)が改行('\n')、NULL文字('\0')でない場合
                // ','を出力して'|'から置き換える
                if (line[i + 1] != '\n' && line[i + 1] != '\0') {
                    fputc(',', out_file);
                }
                break;
            default:
                // '|'以外の文字はそのまま出力する
                fputc(line[i], out_file);
                break;
            }
    
            i++;
        }
    
        // '|' → ','の置換が終わったら改行して次の行へ
        fputc('\n', out_file);
    }
    
[/code]

**sizeof演算子について:**

大雑把に説明すると、sizeof演算子は配列を渡してあげると配列のサイズを返してくれます。  
(今回の場合`line[100]`の`100`を返します)  
これを使用すると、サイズを100から変更する必要が出た時にソースコードを1か所だけ修正すれば済むようになります。

sizeof演算子については別途勉強してみてください。

**似ている処理を関数にまとめる:**

ところで先ほどのコードですが、`○ != '\n' && ○ !=
'\0'`という文末かどうかを判定している処理が2か所にあります。これらは`isEndOfLine(○)`という関数にまとめられそうです。

[code]

    ・・・
        while (fgets(line, sizeof line, in_file) != NULL) {
            i = 1;
    
            // 文字が文末でない間、ループする
            while (isEndOfLine(line[i]) == 0) {
                switch (line[i]) {
                case '|':
                    // '|'の時、次の文字(i+1)が文末でない場合
                    // ','を出力して'|'から置き換える
                    if (isEndOfLine(line[i + 1]) == 0) {
                        fputc(',', out_file);
                    }
                    break;
                default:
                    // '|'以外の文字はそのまま出力する
                    fputc(line[i], out_file);
                    break;
                }
    
                i++;
            }
    
            // '|' → ','の置換が終わったら改行して次の行へ
            fputc('\n', out_file);
        }
    
        fclose(in_file);
        fclose(out_file);
        return 0;
    }
    
    /**
     * 文字が文末かどうかを判定する
     * @param c 文字
     * @return 文字が改行('\n')かNULL文字('\0')の場合、1を返す。それ以外の場合、0を返す
     */
    int isEndOfLine(char c) {
        if (c == '\n' || c == '\0') {
            return 1;
        }
        return 0;
    }
    
[/code]

