Estoy tratando de leer desde un archivo CSV y almacenar cada campo en una variable dentro de una estructura. Estoy usando fgets y strtok para separar cada campo. Sin embargo, no puedo manejar un campo especial que incluye una coma dentro del campo.

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);
    }
}

Las entradas son las siguientes:

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

Intenté imprimir los tokens con este código y obtengo esto:

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

No estoy seguro de cómo manejar el campo de dirección, ya que algunos de ellos pueden tener una coma dentro. Intenté leer carácter por carácter, pero no estoy seguro de cómo insertar las cadenas en la estructura usando un solo bucle. ¿Alguien puede ayudarme con algunas ideas sobre cómo solucionar este problema?

1
lucypher9099 24 ago. 2020 a las 14:39

2 respuestas

La mejor respuesta

strcspn se puede usar para buscar comillas dobles o comillas dobles más una coma.
La cadena original no se modifica, por lo que se pueden utilizar literales de cadena.
La posición de las comillas dobles no es significativa. Pueden estar en cualquier campo.

#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;
}

EDITAR: copiar a variables
Se puede utilizar un contador para realizar un seguimiento de los campos a medida que se procesan.

#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;
}
1
user3121023 24 ago. 2020 a las 16:43

En mi opinión, este tipo de problema requiere un tokenizador "adecuado", quizás basado en una máquina de estados finitos (FSM). En este caso, escanearía la cadena de entrada carácter por carácter, asignando cada carácter a una clase. El tokenizador comenzaría en un estado particular y, según la clase del carácter leído, podría permanecer en el mismo estado o moverse a un nuevo estado. Es decir, las transiciones de estado están controladas por la combinación del estado actual y el carácter en consideración.

Por ejemplo, si lee una comilla doble en el estado inicial, pasa al estado "en una cadena entre comillas". En ese estado, la coma no causaría una transición a un nuevo estado, simplemente se agregaría al token que está construyendo. En cualquier otro estado, la coma tendría un significado particular, ya que denota el final de un token. Tendrías que averiguar cuándo necesitas tragar espacios en blanco adicionales entre tokens, si hubo algún "escape" que permitiera usar una comilla doble en algún otro token, si podrías escapar del final de línea para hacer líneas más largas y así sucesivamente.

El punto importante es que, si implementa esto es un FSM (u otro tokenizador real), en realidad puede considerar todas estas cosas e implementarlas según sea necesario. Si usa aplicaciones ad-hoc de strtok () y búsqueda de cadenas, no puede, al menos no de una manera elegante y fácil de mantener.

Y si, un día, termina necesitando hacer todo el trabajo utilizando caracteres anchos, es fácil: simplemente convierta la entrada en caracteres anchos e itere un carácter ancho (no byte) a la vez.

Es fácil documentar el comportamiento de un analizador FSM utilizando un diagrama de transición de estado; al menos, es más fácil que tratar de explicarlo documentando el código en texto.

Mi experiencia es que la primera vez que alguien implementa un tokenizador FSM, es horrible. Después de eso, es fácil. Y puede utilizar la misma técnica para analizar la entrada de mucha mayor complejidad cuando conoce el método.

0
Kevin Boone 24 ago. 2020 a las 13:39