从 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) 的。在这种情况下,您将逐个字符地扫描输入字符串,将每个字符分配给 class。分词器将以特定状态开始,根据读取字符的 class,它可能会保持相同状态,或移动到新状态。也就是说,状态转换由当前状态和正在考虑的字符的组合控制。
例如,如果您在起始状态下读取 double-quote,则会转换到“在带引号的字符串中”状态。在那种状态下,逗号不会导致转换到新状态——它只会被添加到您正在构建的令牌中。在任何其他状态下,逗号都具有特殊的意义,因为它表示令牌的结尾。您必须弄清楚何时需要在标记之间吞下额外的空格,是否存在允许 double-quote 在其他标记中使用的一些“转义”,是否可以转义 end-of-line制作更长的线条,等等。
重要的一点是,如果您实现的是 FSM(或另一个真正的分词器),您实际上可以考虑所有这些事情,并根据需要实现它们。如果您使用 strtok() 和字符串搜索的 ad-hoc 应用程序,则不能——无论如何,这不是一种优雅、可维护的方式。
如果有一天,您最终需要使用宽字符来完成整个工作,那很容易——只需将输入转换为宽字符并一次迭代一个宽字符(而不是字节)。
使用状态转换图记录 FSM 解析器的行为很容易——至少比尝试通过在文本中记录代码来解释它更容易。
我的经验是,有人第一次实施 FSM 分词器时,感觉很糟糕。在那之后,就很容易了。当您知道该方法时,您可以使用相同的技术来解析更复杂的输入。
我正在尝试读取 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) 的。在这种情况下,您将逐个字符地扫描输入字符串,将每个字符分配给 class。分词器将以特定状态开始,根据读取字符的 class,它可能会保持相同状态,或移动到新状态。也就是说,状态转换由当前状态和正在考虑的字符的组合控制。
例如,如果您在起始状态下读取 double-quote,则会转换到“在带引号的字符串中”状态。在那种状态下,逗号不会导致转换到新状态——它只会被添加到您正在构建的令牌中。在任何其他状态下,逗号都具有特殊的意义,因为它表示令牌的结尾。您必须弄清楚何时需要在标记之间吞下额外的空格,是否存在允许 double-quote 在其他标记中使用的一些“转义”,是否可以转义 end-of-line制作更长的线条,等等。
重要的一点是,如果您实现的是 FSM(或另一个真正的分词器),您实际上可以考虑所有这些事情,并根据需要实现它们。如果您使用 strtok() 和字符串搜索的 ad-hoc 应用程序,则不能——无论如何,这不是一种优雅、可维护的方式。
如果有一天,您最终需要使用宽字符来完成整个工作,那很容易——只需将输入转换为宽字符并一次迭代一个宽字符(而不是字节)。
使用状态转换图记录 FSM 解析器的行为很容易——至少比尝试通过在文本中记录代码来解释它更容易。
我的经验是,有人第一次实施 FSM 分词器时,感觉很糟糕。在那之后,就很容易了。当您知道该方法时,您可以使用相同的技术来解析更复杂的输入。