说明
项目中遇到的问题:
在csv文件中,如果存储文件某行是以下情况时:
ax,by,cz,,
也就是表格最大列的数据为空时,使用strtok()分割读取并显示时会出现:
ax,by,cz, #莫名其妙的将读出的空表格转为'\n'
,
#下一行数据
此文举例并使用strsep()替代strtok()解决办法,并提供strsep()源码,可在windows系统下使用.
提示:提供的代码例程只是项目的替代品,各个例程之间可能联系不大,请见谅。
项目环境
STM32F4 + RL-RTX + emWin + RL-FlashFS
存储类型
TF(microSD)卡
.csv文件格式
假若文件中存储当月日历,理想存储效果如下:
一 | 二 | 三 | 四 | 五 | 六 | 日 |
---|---|---|---|---|---|---|
24 龙头节 | 25 初三 | 26 初四 | 27 初五 | 28 初六 | 29 初七 | 1 初八 |
2 初九 | 3 初十 | 4 十一 | 5 惊蛰 | 6 十三 | 7 十四 | 8 妇女节 |
9 十六 | 10 十七 | 11 十八 | 12 植树节 | 13 二十 | 14 廿一 | 15 廿二 |
16 廿三 | 17 廿四 | 18 廿五 | 19 廿六 | 20 春分 | 21 廿八 | 22 廿九 |
23 三十 | 24 初一 | 25 初二 | 26 初三 | 27 初四 | 28 初五 | 29 初六 |
30 初七 | 31 初八 | 1 愚人节 | 2 初十 | 3 十一 | 4 清明 | 5 十三 |
对csv文件百度百科给出定义:
csv:逗号分隔值或者字符分割值,其文件以纯文本的形式存储表格数据。
其实使用excel打开.csv文件如上表所示,若用记事本打开,则是这种存储格式:
24\n龙头节,25\n初三,26\n初四,27\n初五,28\n初六,29\n初七,1\n初八,
2\n初九,3\n初十,4\n十一,5\n惊蛰,6\n十三,7\n十四,8\n妇女节,
......
实现代码
文件写入:
/* M0:\\ 为sd卡的根目录 */
#define FILE_NAME "M0:\\sdate3.csv"
int TEST_WriteSDFile(void *parg)
{
int ret;
FILE *fout = NULL;
/* 防止编译器出错误 */
parg = parg;
/* 加载SD卡 */
ret = finit("M0:");
if(ret != NULL){
ret = -1;
goto err;
}
/* 从文件中读取文件 */
fout = fopen(FILE_NAME, "a");
if (fout != NULL){
fprintf(fout, "%d\n%s,", 24, "龙头节");
/* 检错 */
if (ferror(fout) != NULL){
return -2;
}
fclose(fout);
}
err:
/* 卸载SD卡 */
funinit("M0:");
return ret;
}
本例程只写入第二行、第一列的一格内容。
具体实现可参考《printf打印日历》,替代printf写入文件即可。
文件行读取:
本例子是将.csv文件中的第 xrow 整行的数据存到 buf 中。
#define MAX_LINE_SIZE (1*1024)
/* 将第xrow行的数据存到 pbuf 中 */
int TEST_ReadSDFileRow(int xrow, char *pbuf, int buflen)
{
int ret;
FILE *fin = NULL;
char strLine[MAX_LINE_SIZE];
/* 参数检测 = 此处省略 */
/* 加载SD卡 */
ret = finit("M0:");
if(ret != NULL){
ret = -1;
goto err;
}
/* 从文件中读取文件 */
fin = fopen(FILE_NAME, "r");
if (fin != NULL){
/* 当前测试案例为7行 */
for(i = 0; i < xrow; i++){
/* 将第xrow整行缓存下来 */
fgets(strLine, MAX_LINE_SIZE, fin);
}
/* 清空传入的buf */
memset(pbuf, 0, buflen);
/* 将整行单独摘取出来*/
strcpy(pbuf, strLine);
}
fclose(fin);
}
err:
/* 卸载SD卡 */
funinit("M0:");
return ret;
}
将字符串分割
假若buf中的内容为
ax,by,cz,,
char *result = NULL;
/* 获取第一个子字符串 */
result = strtok(buf, ",");
/* 继续获取其他的子字符串 */
while(result != NULL)
{
/* 此处本是emWIn表格显示到屏幕列表 */
printf("%s ", result);
/* 继续检测下一个 */
result = strtok(NULL, ",");
}
使用strtok()分割字符串后其打印内容为:
ax,by,cz,
,
莫名其妙的将读出的空表格转为’\n’
使用strsep()并不会出现以上问题.
char *result = NULL;
char *p = buf;
/* 假设获取某行第2列的数据 */
while((result=strsep(&p, ",")) != NULL){
/* 此处本是emWIn表格显示到屏幕列表 */
printf("%s ", result);
}
详见《strtok和strsep函数详解》
但在windows环境中没有该库函数,以下是源码替代,在windows系统使用测试通过。
strsep()函数原型
/*
* @cs: The string to be searched
* @ct: The characters to search for
*/
char *strpbrk(const char *cs, const char *ct)
{
const char *sc1, *sc2;
for (sc1 = cs; *sc1 != '\0'; ++sc1) {
for (sc2 = ct; *sc2 != '\0'; ++sc2) {
if (*sc1 == *sc2)
return (char *)sc1;
}
}
return NULL;
}
/*
* @s: The string to be searched
* @ct: The characters to search for
* strsep() updates @s to point after the token, ready for the next call.
* It returns empty tokens, too, behaving exactly like the libc function
* of that name. In fact, it was stolen from glibc2 and de-fancy-fied.
* Same semantics, slimmer shape. ;)
*/
char *strsep(char **s, const char *ct)
{
char *sbegin = *s;
char *end;
if (sbegin == NULL)
return NULL;
end = strpbrk(sbegin, ct);
if (end)
*end++ = '\0';
*s = end;
return sbegin;
}
代码来源: c 源码string.h中的功能实现