在令牌之间没有数据时解析文件

时间:2016-03-14 19:31:14

标签: c parsing struct token delimiter

嘿伙计们,我正在阅读一个文件,并希望将数据解析为一个struct数组。该文件看起来像这样:

Country,City,Area Code,Population
China,Beijing,,21256972
France,Paris,334,3568253
Italy,Rome,,1235682

我想解析数据并将成员分配给文件中的每个相应区域。我在解析第1行和第3行的数据时没有问题。但是如果没有区号并且彼此相邻有两个逗号,则令牌变为空并且我收到错误。我一直在寻找,似乎无法找到解决方案。这是我的代码:

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

int main(void) {
    static struct Locations {
        char country[20];
        char city[20];
        char areaCode[5];
        char population[100];
    } line[2000000];

    // open file
    FILE *Lfile;
    Lfile = fopen("locations.txt", "r");
    if (!Lfile) {
        perror("File Error");
    }

    char buf[100];
    const char delim[2] = ",";
    char *token;

    int i = 0;
    while (fgets(buf, 100, Lfile) != NULL) {
        token = strtok(buf, delim);
        while (token != NULL) {
            strcpy(line[i].country, token);
            token = strtok(NULL, delim);
            strcpy(line[i].city, token);
            token = strtok(NULL, delim);
            strcpy(line[i].areaCode, token); //Error here
            token = strtok(NULL, delim);
            strcpy(line[i].population, token);
            token = strtok(NULL, delim);
        }
        printf("%s %s %s %s\n", line[i].country,
               line[i].city, line[i].areaCode, line[i].population);
        i++;
    }
    return 0;
}

3 个答案:

答案 0 :(得分:1)

您无法使用strtok()拆分CSV文件,因为strtok会将分隔符字符串中的字符序列视为单个分隔符。它仅用于分隔空格分隔的标记。

您无法使用sscanf("%[^,]", ...),因为sscanf期望解析至少一个与,不同的字符。

您可以使用strchr,或者您可以使用<string.h>中的其他功能来实现您的目的:strcspn()

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

/* copy at most len bytes from src to an array size char and
   null terminate it.  Return the length of the resulting C string
   possibly smaller than len if the destination is too small.
*/
size_t strcpymem(char *dest, size_t size, const char *src, size_t len) {
    if (len >= size)
        len = size - 1;
    memcpy(dest, src, len);
    dest[len] = '\0';
    return len;
}

#define RECORD_NUMBER   2000000

int main(void) {
    static struct Locations {
        char country[20];
        char city[20];
        char areaCode[5];
        char population[100];
    } line[RECORD_NUMBER];

    // open file
    FILE *Lfile;
    Lfile = fopen("locations.txt", "r");
    if (!Lfile) {
        perror("File Error");
    }

    char buf[100];

    int i = 0;
    while (i < RECORD_NUMBER && fgets(buf, 100, Lfile) != NULL) {
        char *p = buf;
        int len = strcspn(p, ",\n");
        strcpymem(line[i].country, sizeof line[i].country, p, len);
        p += len;
        if (*p == ',') p++;
        len = strcspn(p, ",\n");
        strcpymem(line[i].city, sizeof line[i].city, p, len);
        p += len;
        if (*p == ',') p++;
        len = strcspn(p, ",\n");
        strcpymem(line[i].areacode, sizeof line[i].areacode, p, len);
        p += len;
        if (*p == ',') p++;
        len = strcspn(p, ",\n");
        strcpymem(line[i].population, sizeof line[i].population, p, len);
        printf("%s %s %s %s\n", line[i].country,
               line[i].city, line[i].areaCode, line[i].population);
        i++;
    }
    return 0;
}

答案 1 :(得分:0)

sscanf()用于扫描零长度字段很麻烦 strtok()结合了相邻的令牌 有时最好的答案是编写一些代码。

形成一个辅助函数来解析子字符串。一些未经测试的代码如下。关键是将代码的功能划分为更容易维护的功能

// Starting a `p`, copy until delimiter found or size exhausted
// Return NULL on failure
const char *foo(const char *p, char *dest, size_t size, int end) {
  if (p) {
    while (size-- > 0 && *p) {
      *dest = *p++;
      if (*dest == end) {
        *dest = '\0';
        return p;
      }
      dest++; 
    }
  }
  return NULL;
} 

重写主循环

// Make buf big enough
char buf[sizeof line[0] * 2];

// Use size_t rather than int
size_t i = 0;
// Limit iteration count
while (i < RECORD_NUMBER && fgets(buf, sizeof buf, Lfile) != NULL) {
  const char *p = buf;
  p = foo(p, line[i].country,    sizeof line[i].country,    ',');
  p = foo(p, line[i].city,       sizeof line[i].city,       ',');
  p = foo(p, line[i].areaCode,   sizeof line[i].areaCode,   ',');
  p = foo(p, line[i].population, sizeof line[i].population, '\n');
  if (p == NULL || *p != '\0') {
    printf("Bad line '%s'\n", buf);
    exit (-1);
  }
  i++;
}

答案 2 :(得分:-1)

我喜欢使用sscanf进行这种解析。

int i = 0;
while (fgets(buf, 100, Lfile) != NULL) {
    char *pbuf = buf;
    int offset;
    pbuf += sscanf( pbuf, "%[^,],%n", line[i].country , &offset) ? offset : 1;
    pbuf += sscanf( pbuf, "%[^,],%n", line[i].city    , &offset) ? offset : 1;
    pbuf += sscanf( pbuf, "%[^,],%n", line[i].areaCode, &offset) ? offset : 1;
    pbuf += sscanf( pbuf, " %s",       line[i].population );

    printf("%s %s %s %s\n", line[i].country,
           line[i].city, line[i].areaCode, line[i].population);
    i++;
}

也许这是较慢的代码,也许它会因格式错误而崩溃。也许它有其他缺陷,但这就是我喜欢做这种解析的方式。

注意:如果解析空白字段,则char指针不会设置为NULL。因此,初始化结构很重要。

编辑:修复了处理空白字段时的错误。