簡體   English   中英

從 CSV 文件中讀取並分離字段以存儲在 C 中的結構中

[英]Reading from a CSV file and separating the fields to store in a struct in C

我正在嘗試從 CSV 文件中讀取並將每個字段存儲到結構內的變量中。 我使用 fgets 和 strtok 來分隔每個字段。 但是,我無法處理在字段內包含逗號的特殊字段。

typedef struct {
    char name[20+1];
    char surname[20+1];
    char uniqueId[10+1];
    char address[150+1];
} employee_t;

void readFile(FILE *fp, employee_t *employees[]){
    int i=0;
    char buffer[205];
    char *tmp;
    
    while (fgets(buffer,205,fp) != NULL) {
        employee_t *new = (employee_t *)malloc(sizeof(*new));
        
        tmp = strtok(buffer,",");
        strcpy(new->name,tmp);
        
        tmp = strtok(buffer,",");
        strcpy(new->surname,tmp);
        
        tmp = strtok(buffer,",");
        strcpy(new->uniqueId,tmp);

        tmp = strtok(buffer,",");
        strcpy(new->address,tmp);

        employees[i++] = new;
        free(new);
    }
}

輸入如下:

Jim,Hunter,9239234245,"8/1 Hill Street, New Hampshire"
Jay,Rooney,92364434245,"122 McKay Street, Old Town"
Ray,Bundy,923912345,NOT SPECIFIED

我嘗試使用此代碼打印令牌並得到以下信息:

Jim 
Hunter 
9239234245
"8/1 Hill Street
 New Hampshire"

我不確定如何處理地址字段,因為其中一些字段中可能有逗號。 我嘗試逐個字符讀取,但不確定如何使用單個循環在結構中插入字符串。 有人可以幫助我提供一些有關如何解決此問題的想法嗎?

strcspn可用於查找雙引號或雙引號加逗號。
原始字符串未修改,因此可以使用字符串文字。
雙引號的位置並不重要。 他們可以在任何領域。

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

int main( void) {

    char *string[] = {
        "Jim,Hunter,9239234245,\"8/1 Hill Street, New Hampshire\""
        , "Jay,Rooney,92364434245,\"122 McKay Street, Old Town\""
        , "Ray,Bundy,923912345,NOT SPECIFIED"
        , "Ray,Bundy,\" double quote here\",NOT SPECIFIED"
    };

    for ( int each = 0; each < 4; ++each) {
        char *token = string[each];
        char *p = string[each];

        while ( *p) {
            if ( '\"' == *p) {//at a double quote
                p += strcspn ( p + 1, "\"");//advance to next double quote
                p += 2;//to include the opening and closing double quotes
            }
            else {
                p += strcspn ( p, ",\"");//advance to a comma or double quote
            }
            int span = ( int)( p - token);
            if ( span) {
                printf ( "token:%.*s\n", span, token);//print span characters

                //copy to another array
            }
            if ( *p) {//not at terminating zero
                ++p;//do not skip consecutive delimiters

                token = p;//start of next token
            }
        }
    }
    return 0;
}

編輯:復制到變量
計數器可用於在處理字段時對其進行跟蹤。

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

#define SIZENAME 21
#define SIZEID 11
#define SIZEADDR 151

typedef struct {
    char name[SIZENAME];
    char surname[SIZENAME];
    char uniqueId[SIZEID];
    char address[SIZEADDR];
} employee_t;

int main( void) {

    char *string[] = {
        "Jim,Hunter,9239234245,\"8/1 Hill Street, New Hampshire\""
        , "Jay,Rooney,92364434245,\"122 McKay Street, Old Town\""
        , "Ray,Bundy,923912345,NOT SPECIFIED"
        , "Ray,Bundy,\"quote\",NOT SPECIFIED"
    };
    employee_t *employees = malloc ( sizeof *employees * 4);
    if ( ! employees) {
        fprintf ( stderr, "problem malloc\n");
        return 1;
    }

    for ( int each = 0; each < 4; ++each) {
        char *token = string[each];
        char *p = string[each];
        int field = 0;

        while ( *p) {
            if ( '\"' == *p) {
                p += strcspn ( p + 1, "\"");//advance to a delimiter
                p += 2;//to include the opening and closing double quotes
            }
            else {
                p += strcspn ( p, ",\"");//advance to a delimiter
            }
            int span = ( int)( p - token);
            if ( span) {
                ++field;
                if ( 1 == field) {
                    if ( span < SIZENAME) {
                        strncpy ( employees[each].name, token, span);
                        employees[each].name[span] = 0;
                        printf ( "copied:%s\n", employees[each].name);//print span characters
                    }
                }
                if ( 2 == field) {
                    if ( span < SIZENAME) {
                        strncpy ( employees[each].surname, token, span);
                        employees[each].surname[span] = 0;
                        printf ( "copied:%s\n", employees[each].surname);//print span characters
                    }
                }
                if ( 3 == field) {
                    if ( span < SIZEID) {
                        strncpy ( employees[each].uniqueId, token, span);
                        employees[each].uniqueId[span] = 0;
                        printf ( "copied:%s\n", employees[each].uniqueId);//print span characters
                    }
                }
                if ( 4 == field) {
                    if ( span < SIZEADDR) {
                        strncpy ( employees[each].address, token, span);
                        employees[each].address[span] = 0;
                        printf ( "copied:%s\n", employees[each].address);//print span characters
                    }
                }
            }
            if ( *p) {//not at terminating zero
                ++p;//do not skip consceutive delimiters

                token = p;//start of next token
            }
        }
    }
    free ( employees);
    return 0;
}

在我看來,這種問題需要一個“合適的”分詞器,可能基於有限狀態機(FSM)。 在這種情況下,您將逐個字符掃描輸入字符串,將每個字符分配給一個類。 分詞器將在特定狀態下啟動,並且根據讀取的字符的類別,它可能保持相同狀態,或移動到新狀態。 也就是說,狀態轉換由當前狀態和所考慮的角色的組合控制。

例如,如果您在起始狀態中讀取雙引號,則會轉換到“在帶引號的字符串中”狀態。 在那種狀態下,逗號不會導致轉換到新狀態——它只會被添加到您正在構建的令牌中。 在任何其他狀態下,逗號將具有特殊意義,表示標記的結尾。 您必須弄清楚何時需要在標記之間吞下額外的空格,是否有一些“轉義”允許在其他標記中使用雙引號,您是否可以轉義行尾更長的線,等等。

重要的一點是,如果您實現的是 FSM(或另一個真正的標記器),您實際上可以考慮所有這些事情,並根據需要實現它們。 如果您使用 strtok() 和字符串搜索的臨時應用程序,則不能——無論如何都不能以優雅的、可維護的方式。

如果有一天,您最終需要使用寬字符完成整個工作,那很容易——只需將輸入轉換為寬字符並一次迭代一個寬字符(而不是字節)。

使用狀態轉換圖記錄 FSM 解析器的行為很容易——至少,比試圖通過記錄文本代碼來解釋它更容易。

我的經驗是,有人第一次實現 FSM 標記器時,這很可怕。 之后,這很容易。 當您知道該方法時,您可以使用相同的技術來解析更復雜的輸入。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM