C言語関係掲示板

過去ログ

No.1227 「""」で囲まれたCSV形式データの読み込み

[戻る] [ホームページ]
No.16232

「""」で囲まれたCSV形式データの読み込み
投稿者---やもと(2004/08/18 12:17:45)


文字列がダブルクォーテーションで囲まれたカンマ区切りのCSV形式データ
からダブルクォーテーションを取り除いた文字列だけを取得しようと
しています。文字列中にカンマが含まれている場合はカンマは有効とします。

データの4行目と5行目にある通り、カンマが文字列の前にあると上手く
取得できません。
どうれば上手くできますか。

-- 読み込みデータ -----
"000001","CODE1","2002.08.26","a1 a2 a3"
"000002","","2002.08.26",""
"000003","","2002.08.26","a1,a2"
"000004","CODE2","2002.08.26",",a1"
"000005",",CODE2","2002.08.26","a1"
"000006","","2002.08.26","a1,"

-- 結果 ----
:000001:CODE1:2002.08.26:a1 a2 a3:
:000002::2002.08.26::
:000003::2002.08.26:a1,a2:
:000004:CODE2:2002.08.26:: → :000004:CODE2:2002.08.26:,a1: を期待
:000005::CODE2:2002.08.26: → :000005:,CODE2:2002.08.26:a1: を期待
:000006::2002.08.26:a1,:

-- ソース ----
#include <stdio.h>
#include <string.h>

char *setvalue(char *, char *, int) ;

typedef struct {
    char str1[64] ;
    char str2[64] ;
    char str3[64] ;
    char str4[64] ;
} TEMP_DATA ;

main()
{
    FILE *fp ;
    TEMP_DATA temp_data ;
    char buff[1024], *p ;

    memset(buff,'\0',sizeof(buff)) ;
    while(fgets(buff,sizeof(buff),stdin)!=NULL) {
        p = setvalue(buff, temp_data.str1, sizeof(temp_data.str1)) ;
        p = setvalue(p+2, temp_data.str2, sizeof(temp_data.str2)) ;
        p = setvalue(p+2, temp_data.str3, sizeof(temp_data.str3)) ;
        p = setvalue(p+2, temp_data.str4, sizeof(temp_data.str4)) ;

        printf(":%s:%s:%s:%s:\n",
        temp_data.str1, temp_data.str2, temp_data.str3, temp_data.str4) ;
    }

    return 0 ;
}

char *setvalue(s, t, size)
    char *s ;
    char *t ;
    int  size ;
{
    char *e  ;

    e = t + size - 1 ;

    while (t < e && *s != '\0' && *s != '\n') {
        if (*s == '"' && *(s+1) != ',') {
            *s++ ;
            continue ;
        }
        if (*s == '"' && *(s+1) == ',') {
            break ;
        }
        *t++ = *s++ ;
    }
    *t = '\0' ;

    return s ;
}






No.16233

Re:「""」で囲まれたCSV形式データの読み込み
投稿者---Sciggepy(2004/08/18 12:47:42)


<pre>> if (*s == '"' && *(s+1) != ',') {
> *s++ ;
> continue ;
> }
> if (*s == '"' && *(s+1) == ',') {
> break ;
> }
</pre>'"'の隣に','があるかどうかで分岐していますよね。これでは、
",???"という形のデータを読み込もうとした際、",の部分でループを脱出してしまい、次の呼び出しでは、???の部分が読み込まれてしまいます。

最初の'"'を読み込んだら、最後の'"'を読み込むまでループを脱出しないようにする必要があります。'"'を文字として読み込むには、'\"'とする必要もあるかと思います。



No.16234

Reできました。少し良い方法はないでようか。
投稿者---やもと(2004/08/18 14:29:02)


ありがとうございます。

書込みを元に考えてみました。
なんとか上手く動くようになりました。
書き方というか、見栄えというか、もう少し良い処理が出来るのでは
ないかと思うのですが、なにかないでしょうか。

char *setvalue(s, t, size)
    char *s ;
    char *t ;
    int  size ;
{
    char *e  ;
    int flg = 0 ;

    e = t + size - 1 ;

    while (t < e && *s != '\0' && *s != '\n') {
        if (*s == '\"' && flg == 0) {
            flg = 1 ;
            *s++ ;
            continue ;
        }
        if (*s == '\"' && *(s+1) == ',' && *(s+2) == '\"') {
            break ;
        }
        if (*s == '\"' && *(s+1) == '\n') {
            break ;
        }
        *t++ = *s++ ;
    }
    *t = '\0' ;

    return s ;
}




No.16235

Re:Reできました。少し良い方法はないでようか。
投稿者---nop(2004/08/18 15:24:14)


詳細なデバッグはしてないが、
こんな感じでいかがだろう?

------------------------------------------------------------
#include <stdio.h>
#include <string.h>

char *setvalue(char *, char *, int);

typedef struct
{
    char str1[64];
    char str2[64];
    char str3[64];
    char str4[64];
} TEMP_DATA;

int main( void )
{
    FILE *fp;
    TEMP_DATA temp_data;
    char buff[1024], *p;

    memset(buff,'\0',sizeof(buff));

    while(fgets(buff,sizeof(buff),stdin)!=NULL)
    {
        /* ----- 改行を削除 ----- */
        ( p=strrchr(buff,'\n') ) ? *p='\0': 0;

        p = setvalue( buff, temp_data.str1, sizeof(temp_data.str1) );
        p = setvalue( p,    temp_data.str2, sizeof(temp_data.str2) );
        p = setvalue( p,    temp_data.str3, sizeof(temp_data.str3) );
        p = setvalue( p,    temp_data.str4, sizeof(temp_data.str4) );
        printf( ":%s:%s:%s:%s:\n", temp_data.str1, temp_data.str2, temp_data.str3, temp_data.str4);
    }
    return 0;
}

char *setvalue( char *s, char *t, int size )
{
    /* ----- 内部変数定義 ----- */
    char  iFlag = 0;
    int   i;

    for( i=0; i<size && *s && !(!iFlag && *s==','); i++,s++ )
    {
        switch( *s )    /* 文字判定 */
        {
        case '\"':
            /* ----- フラグを反転 ----- */
            iFlag = !iFlag;
            break;
        case ',':
            if( iFlag ) /* 「"」で括られた文字列外の「,」か? */
            {
                /* ----- 文字をコピー ----- */
                *t++ = *s;
            }
            break;
        case '\\':
            /* ----- 次の文字へ ----- */
            s++;
        default:
            /* ----- 文字をコピー ----- */
            *t++ = *s;
            break;
        }
    }
    /* ----- ヌルターミネート ----- */
    *t = '\0';

    return s+(*s==',');
}



No.16239

Re:Reできました。少し良い方法はないでようか。
投稿者---やもと(2004/08/18 22:26:51)


ありがとうございます。
このような処理の方法もあるのですね。


一例として載せていただいたソースのロジックについて
教えて下さい。
( p=strrchr(buff,'\n') ) ? *p='\0': 0;
for( i=0; i<size && *s && !(!iFlag && *s==','); i++,s++ )
return s+(*s==',');



の3箇所の使い方、動きについて教えて下さい。


No.16240

Re:Reできました。少し良い方法はないでようか。
投稿者---RAPT(2004/08/19 00:46:55)


( p=strrchr(buff,'\n') ) ? *p='\0': 0;
3項演算子。「式 ? 真の場合 : 偽の場合」
今回のケースでは、下記と同等。
# というか、私だったら、普通にif文で書きますが。

p = strrchr(buff, '\n');
if( p != NULL ){
  *p = '\0';
}


for( i=0; i<size && *s && !(!iFlag && *s==','); i++,s++ )
「*s && !(!iFlag && *s==',')」の部分かな?
これは、「A かつ B」は「Aでない または Bでない」と同等である事から、
「*s != '\0' && (iFlag == 0 || *s != ',')」と同等。


return s+(*s==',');
これは、下記と同等。
# C言語では、式の評価結果が真の場合は 1、偽の場合は 0 となる。

if(*s == ','){
  return s + 1;
}else{
  return s;
}


コードの読み方は以上。その意味付けはご自分で。



No.16241

Re:Reできました。少し良い方法はないでようか。
投稿者---ごんべ(2004/08/19 10:53:58)


>for( i=0; i<size && *s && !(!iFlag && *s==','); i++,s++ )
>「*s && !(!iFlag && *s==',')」の部分かな?
>これは、「A かつ B」は「Aでない または Bでない」と同等である事から、
>「*s != '\0' && (iFlag == 0 || *s != ',')」と同等。

全体のソースを見ていないのでなんともいえませんが、
「*s != '\0' && (iFlag == 0 || *s != ',')」

「*s != '\0' && (iFlag != 0 || *s != ',')」
ではないでしょうか。



No.16291

Re:Reできました。少し良い方法はないでようか。
投稿者---RAPT(2004/08/21 22:40:34)


>>for( i=0; i<size && *s && !(!iFlag && *s==','); i++,s++ )
>>「*s && !(!iFlag && *s==',')」の部分かな?
>>これは、「A かつ B」は「Aでない または Bでない」と同等である事から、
訂正。
「(A かつ B)でない」は「Aでない または Bでない」と同等 …★
ですね。書きミスでした。

>全体のソースを見ていないのでなんともいえませんが、
>「*s != '\0' && (iFlag == 0 || *s != ',')」
>は
>「*s != '\0' && (iFlag != 0 || *s != ',')」
>ではないでしょうか。
そのとおりです。フォローありがとうございます。
お詫びに、数学の証明のような書き方で繙いてみました。


>>「*s && !(!iFlag && *s==',')」
→ (*s != 0) && !(!(iFlag != 0) && (*s == ','))
→ (*s != 0) && !( (iFlag == 0) && (*s == ','))

ここで、「!( (iFlag == 0) && (*s == ','))」について解くと、
A…(iFlag == 0)
B…(*s == ',')
とすると、
!( (iFlag == 0) && (*s==',') ) は、
!(      A     かつ    B    )となり、★より、
 (     !A    または  !B    ) 
( !(iFlag == 0) || !(*s == ',') )
(  (iFlag != 0) ||  (*s != ',') )

となる。従って、
→ (*s != 0) && ((iFlag != 0) || (*s != ','))
となる。



No.16271

文字列中にダブルクォーテーションが存在すると
投稿者---よすみ(2004/08/21 09:42:59)


ダブルクォーテーション「"」が文字列中に文字として存在している
場合だと、載せていただいたソースでは上手くいきません。
どうすればよいですか。

/*-------------------------------------------------------*/
char *setvalue(s, t, size)
    char *s ;
    char *t ;
    int  size ;
{
    char *e  ;
    int flg = 0 ;

    e = t + size - 1 ;

    while (t < e && *s != '\0' && *s != '\n') {
        if (*s == '\"' && flg == 0) {
            flg = 1 ;
            *s++ ;
            continue ;
        }
        if (*s == '\"' && *(s+1) == ',' && *(s+2) == '\"') {
            break ;
        }
        if (*s == '\"' && *(s+1) == '\n') {
            break ;
        }
        *t++ = *s++ ;
    }
    *t = '\0' ;

    return s ;
}


/*-------------------------------------------------------*/
char *setvalue( char *s, char *t, int size )
{
    /* ----- 内部変数定義 ----- */
    char  iFlag = 0;
    int   i;

    for( i=0; i<size && *s && !(!iFlag && *s==','); i++,s++ )
    {
        switch( *s )    /* 文字判定 */
        {
        case '\"':
            /* ----- フラグを反転 ----- */
            iFlag = !iFlag;
            break;
        case ',':
            if( iFlag ) /* 「"」で括られた文字列外の「,」か? */
            {
                /* ----- 文字をコピー ----- */
                *t++ = *s;
            }
            break;
        case '\\':
            /* ----- 次の文字へ ----- */
            s++;
        default:
            /* ----- 文字をコピー ----- */
            *t++ = *s;
            break;
        }
    }
    /* ----- ヌルターミネート ----- */
    *t = '\0';

    return s+(*s==',');

}



No.16274

Re:文字列中にダブルクォーテーションが存在すると
投稿者---ぽこ(2004/08/21 12:12:50)


>ダブルクォーテーション「"」が文字列中に文字として存在している
>場合だと、載せていただいたソースでは上手くいきません。
>どうすればよいですか。

ダブルクォーテーションはデータを囲むメタ文字と見なしていますから、
上手く行かないのは当然です。
(文字としてのダブルコーテーションという概念が存在しないため。)

上手く行かせるには文字としてのダブルコーテーションと、
メタ文字としてのダブルコーテーションを区別するためのルールを
定める必要があります。



No.16275

Re:文字列中にダブルクォーテーションが存在すると
投稿者---Sciggepy(2004/08/21 12:30:57)


>ダブルクォーテーション「"」が文字列中に文字として存在している
>場合だと、載せていただいたソースでは上手くいきません。
>どうすればよいですか。
「\"」と書いてダブルクォーテーションを表すことが多いようです。



No.16282

ありがとうございます。
投稿者---よすみ(2004/08/21 15:13:41)


ありがとうございます。

CSVデータのルール、出力方法で対応をします。



No.16305

Re:文字列中にダブルクォーテーションが存在すると
投稿者---ごんべ(2004/08/23 00:18:46)


「\"」とした場合です。


char *setvalue( char *s, char *t, int size )
{
    /* ----- 内部変数定義 ----- */
    char  iFlag = 0;
    int   i;

    for( i=0; i<size && *s != '\0' && (iFlag != 0 || *s != ','); i++,s++ )
    {
        switch( *s )    /* 文字判定 */
        {
        case '\"':
            if( iFlag == 1 && *(s-1) == '\\' ) /* 「"」で括られた文字列の「"」か? */
            {
                *t++ = *s;
            }
            /* ----- フラグを反転 ----- */
            else
            {
                iFlag = !iFlag;
            }
            break;
        case ',':
            // if( iFlag ) /* 「"」で括られた文字列外の「,」か? */
            if( iFlag == 1 ) /* 「"」で括られた文字列外の「,」か? */
            {
                /* ----- 文字をコピー ----- */
                *t++ = *s;
            }
            break;
        case '\\':
            /* ----- 次の文字へ ----- */
            s++;
        default:
            /* ----- 文字をコピー ----- */
            *t++ = *s;
            break;
        }
    }
    /* ----- ヌルターミネート ----- */
    *t = '\0';

    if (*s == ',') {
        return s + 1;
    } else {
        return s;
    }
}




No.16306

Re:文字列中にダブルクォーテーションが存在すると
投稿者---nop(2004/08/23 10:44:41)


>「\"」とした場合です。

え〜っと…
いちお、私が書いたソースは、
「\」文字によるエスケープに対応したものなので、
修正は必要ないはずですが…?(^^;)ゞ

# いちお、そこの動作確認も簡単ながら、
# さらっとやっているのですが…(^^;



No.16322

Re:文字列中にダブルクォーテーションが存在すると
投稿者---ごんべ(2004/08/24 00:15:46)



># いちお、そこの動作確認も簡単ながら、
># さらっとやっているのですが…(^^;

すみません。
一度、動かしたとき、上手く行かなかったもので。
環境がわるいのなか。
データかな。



No.16316

Re:文字列中にダブルクォーテーションが存在すると
投稿者---かずま(2004/08/23 18:46:06)


> ダブルクォーテーション「"」が文字列中に文字として存在している
> 場合だと、載せていただいたソースでは上手くいきません。
> どうすればよいですか。

CSV の仕様は、

・一行の中のデータは ,(カンマ)で区切る。行の終りは改行。
・データがカンマやダブルクォートを含む場合は "(ダブルクォート)で囲む。
・データの中のダブルクォートはそれをダブルクォート2個("")で置き換える。

実際、Excel で、ab"cd を CSV で保存すると、"ab""cd" になります。
#include <stdio.h>

char * setvalue(char *p, char *t, int size)
{
    if (*p == '"')
        while (*++p && *p != '\n' && (*p != '"' || *++p == '"')) {
            if (--size > 0) *t++ = *p;
        }
    else
        for (; *p && *p != ',' && *p != '\n'; p++)
            if (--size > 0) *t++ = *p;
    *t = '\0';
    return *p ? p+1 : p;
}

int main(void)
{
    char buf[256], field[256], *p;

    while (fgets(buf, sizeof buf, stdin)) {
        for (p = buf; *p; ) {
            p = setvalue(p, field, sizeof field);
            printf(":%s", field);
        }
        puts(":");
    }
    return 0;
}



No.16320

Re:文字列中にダブルクォーテーションが存在すると
投稿者---かずま(2004/08/23 21:17:14)


" で始まるデータは " で終わらなければならないのですが、万一、"ab"cde
のようなデータがあった場合、ab と de の 2つのデータがあるかのような
結果を出すのは変なので、次のように修正します。
char * setvalue(char *p, char *t, int size)
{
    if (*p == '"')
        while (*++p && *p != '\n' && (*p != '"' || *++p == '"'))
            if (--size > 0) *t++ = *p;
    for (; *p && *p != ',' && *p != '\n'; p++)
        if (--size > 0) *t++ = *p;
    *t = '\0';
    return *p ? p+1 : p;
}



No.16321

Re:文字列中にダブルクォーテーションが存在すると
投稿者---よすみ(2004/08/23 21:51:22)


ありがとうございす。

データの中のダブルクォートは、\印をつけて文字とする場合は、
("abc\"def,ghi"のような場合は、)サンプルとして載せて頂いた、
ダブルクォートの個所を\印に置き換えばよいのですか。


No.16323

Re:文字列中にダブルクォーテーションが存在すると
投稿者---かずま(2004/08/24 03:44:55)


> データの中のダブルクォートは、\印をつけて文字とする場合は、
> ("abc\"def,ghi"のような場合は、)サンプルとして載せて頂いた、
> ダブルクォートの個所を\印に置き換えばよいのですか。

いいえ。\" という記法は CSV にはありません。

"abc\"def,ghi" というデータを含む CSVファイルを Excel でオープンすると、
abc\def と ghi" という 2つのデータと解釈されます。

abc"def,ghi という 1つのデータとして解釈して欲しいなら、
"abc""def,ghi" と書かないといけません。それが CSV のルールです。

CSVファイルを読み取るプログラムは、Kernighan & Pike の The Practice of
Programming (邦訳「プログラミング作法」) の第4章にあります。

CSV の仕様をきちんと決めた規格書の存在は知りませんが、Web を探せば
CSV を解析するプログラムが見つかります。例えば、
http://www.nsknet.or.jp/~kamichan/prog/CSV_pm.html
C ではありませんが。


No.16324

ありがとうございます。
投稿者---よすみ(2004/08/24 10:25:09)


かずまさん、nopさん、ごんべさん
ありがとうございます。

意見を参考にどうすればベストか、データの内容、ソース等
を考えてみます。



No.16325

Re:ありがとうございます。
投稿者---Sciggepy(2004/08/24 15:07:49)


「CSVの仕様」は見つからなかったのですが、確かにExcelでは'"'を'""'で表していました。

#include <stdio.h>

int main(void)
{
    char *src="54,\"we,e\",\"\"\"ese\"\"\"",*p,buf[8];
    int q=0,i=0;

    printf("src: %s\n",src);
    for(;;src++) {
            if(*src=='\0') {
            buf[i]='\0';
                printf("%s\n",buf);
            break;
        }
        if(q) {
            if(*src=='\"') {
                if(*(src+1)=='\"') {
                    if(i<7) buf[i++]=*(src++);
                    continue;
                }
                else q=0;
            } else if(i<7) buf[i++]=*src;
        } else {
            if(*src=='\"') q=1;
            else if(*src==',') {
                buf[i]='\0';
                    printf("%s\n",buf);
                i=0;
            } else if(i<7) buf[i++]=*src;
        }
    }
    return 0;
}



No.16326

Re:ありがとうございます。
投稿者---nop(2004/08/24 15:36:28)


>「CSVの仕様」は見つからなかったのですが、確かにExcelでは'"'を'""'で表していました。

こんなの見つけたけど、どう?
File Format


No.16328

Re:ありがとうございます。
投稿者---Sciggepy(2004/08/24 17:36:37)


了解しました。
しかし、結局のところ、「規格化された仕様」というのはないようですね。