C 封装一个csv 解析库
引言
最经关于基础C开发框架基本都搭建好了. 在研究githup,准备传上去. 可惜的是两会连githup 都登陆不进去.
三观很正的我也觉得, 这样不好. 双向标准, 共x党不是一个代表穷苦大众的党.当然我也恒感谢党国, 给我选举权,每次都是
人大代表帮我投了,好人. 谢谢了!
后面可能没办法, 继续上传到 csdn 上. 会把使用手册,注意事项写清楚.这个框架,适合新手参考吧.大多库还是很复杂的
内力不足看多了容易走火入魔.我这里提供都比较浅显易懂. 适合使用. 感受简单,高效,能用,实在的设计.
杀死那个石家庄人 http://music.163.com/#/song?id=386844
前言
同样先介绍一写,这节要将的精华.首先说一下大白文,将读取文件的内容直到全部.
/*
* 简单的文件帮助类,会读取完毕这个文件内容返回,失败返回NULL.
* 需要事后使用 tstring_destroy(&ret); 销毁这个字符串对象
* path : 文件路径
* ret : 返回创建好的字符串内容,返回NULL表示读取失败
*/
tstring file_malloc_readend(const char* path)
{
int c;
tstring tstr;
FILE* txt = fopen(path, "r");
if (NULL == txt) {
SL_NOTICE("fopen r path = '%s' error!", path);
return NULL;
} //这里创建文件对象,创建失败直接返回
if ((tstr = tstring_create(NULL)) == NULL) {
fclose(txt);
return NULL;
} //这里读取文本内容
while ((c = fgetc(txt))!=EOF)
if (_RT_OK != tstring_append(tstr, c)){ //出错了就直接销毁已经存在的内容
tstring_destroy(&tstr);
break;
} fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug
return tstr;
}
一个细节是 加了字符串数据 返回判断,如果内存分配失败直接返回.
还有一个值得学习的细节是 只能在堆上分配的内存结构
/*
* 这里是一个解析 csv 文件的 简单解析器.
* 它能够帮助我们切分文件内容,保存在数组中.
*/
struct sccsv { //内存只能在堆上
int rlen; //数据行数,索引[0, rlen)
int clen; //数据列数,索引[0, clen)
const char* data[]; //保存数据一维数组,希望他是二维的 rlen*clen
}; typedef struct sccsv* sccsv_t;
上面是新语法, 以前的做法是data[0], data[1]等. 在结构体中声明可变数组.这种结构是不完全结构无法 直接 struct sccsv 在堆上声明.
这里基本上就是我们说的. 再扯一点当你使用inline语法在C中的时候. 一种是static inline 内联.一种如下内联声明
/*
* 获取某个位置的对象内容,这个函数 推荐声明为内联的, window上不支持
* csv : sccsv_t 对象, new返回的
* ri : 查找的行索引 [0, csv->rlen)
* ci : 查找的列索引 [0, csv->clen)
* : 返回这一项中内容,后面可以用 atoi, atof, str_dup 等处理了...
*/
extern inline const char* sccsv_get(sccsv_t csv, int ri, int ci);
到这里基本C基础普及就这样了,等一下分析正文.
正文
那就开始正题描述吧.首先什么是csv文件. 对比显差异.预览图

再看看实际的编码图

通过这个看应该就知道csv文件的编码规则了吧. 总结如下
1.用 , 分割
2.如果出现 , " 这种特殊字符, 会被用 "" 包裹起来, 并且 "" 表示一个 " 号
3.每行用\r\n结束
这样语法问题都已经解决了.
再分析我们今天的 接口内容 sccsv.h
#ifndef _H_SCCSV
#define _H_SCCSV /*
* 这里是一个解析 csv 文件的 简单解析器.
* 它能够帮助我们切分文件内容,保存在数组中.
*/
struct sccsv { //内存只能在堆上
int rlen; //数据行数,索引[0, rlen)
int clen; //数据列数,索引[0, clen)
const char* data[]; //保存数据一维数组,希望他是二维的 rlen*clen
}; typedef struct sccsv* sccsv_t; /*
* 从文件中构建csv对象, 最后需要调用 sccsv_die 释放
* path : csv文件内容
* : 返回构建好的 sccsv_t 对象
*/
extern sccsv_t sccsv_new(const char* path); /*
* 释放由sccsv_new构建的对象
* pcsv : 由sccsv_new 返回对象
*/
extern void sccsv_die(sccsv_t* pcsv); /*
* 获取某个位置的对象内容,这个函数 推荐声明为内联的, window上不支持
* csv : sccsv_t 对象, new返回的
* ri : 查找的行索引 [0, csv->rlen)
* ci : 查找的列索引 [0, csv->clen)
* : 返回这一项中内容,后面可以用 atoi, atof, str_dup 等处理了...
*/
extern inline const char* sccsv_get(sccsv_t csv, int ri, int ci); #endif // !_H_SCCSV
构建销毁获得指定内容. 很容易理解.
现在我们展示一下运行的结果, 测试代码是
#include <schead.h>
#include <sclog.h>
#include <sccsv.h> #define _STR_PATH "onetime.csv"
// 解析 csv文件内容
int main(int argc, char* argv[])
{
sccsv_t csv;
int i, j;
int rlen, clen; INIT_PAUSE();
sl_start(); // 这里得到 csv 对象
csv = sccsv_new(_STR_PATH);
if (NULL == csv)
CERR_EXIT("open " _STR_PATH " is error!"); //这里打印数据
rlen = csv->rlen;
clen = csv->clen;
for (i = ; i < rlen; ++i) {
for (j = ; j < clen; ++j) {
printf("<%d, %d> => [%s]\n", i, j, sccsv_get(csv, i, j));
}
} //开心 测试圆满成功
sccsv_die(&csv);
return ;
}
最后运行的预览图

运行起来可能复杂一点点, 这里摘录一下编译图,还是看代码吧,你自己找其中关于 test_csv.c 文件的编译过程吧
C = gcc
DEBUG = -g -Wall -D_DEBUG
#指定pthread线程库
LIB = -lpthread -lm
#指定一些目录
DIR = -I./module/schead/include -I./module/struct/include
#具体运行函数
RUN = $(CC) $(DEBUG) -o $@ $^ $(LIB) $(DIR)
RUNO = $(CC) $(DEBUG) -c -o $@ $^ $(DIR) # 主要生成的产品
all:test_cjson_write.out test_csjon.out test_csv.out test_json_read.out test_log.out\
test_scconf.out test_tstring.out #挨个生产的产品
test_cjson_write.out:test_cjson_write.o schead.o sclog.o tstring.o cjson.o
$(RUN)
test_csjon.out:test_csjon.o schead.o sclog.o tstring.o cjson.o
$(RUN)
test_csv.out:test_csv.o schead.o sclog.o sccsv.o tstring.o
$(RUN)
test_json_read.out:test_json_read.o schead.o sclog.o sccsv.o tstring.o cjson.o
$(RUN)
test_log.out:test_log.o schead.o sclog.o
$(RUN)
test_scconf.out:test_scconf.o schead.o scconf.o tree.o tstring.o sclog.o
$(RUN)
test_tstring.out:test_tstring.o tstring.o sclog.o schead.o
$(RUN) #产品主要的待链接文件
test_cjson_write.o:./main/test_cjson_write.c
$(RUNO)
test_csjon.o:./main/test_csjon.c
$(RUNO)
test_csv.o:./main/test_csv.c
$(RUNO)
test_json_read.o:./main/test_json_read.c
$(RUNO)
test_log.o:./main/test_log.c
$(RUNO) -std=c99
test_scconf.o:./main/test_scconf.c
$(RUNO)
test_tstring.o:./main/test_tstring.c
$(RUNO) #工具集机械码,待别人链接
schead.o:./module/schead/schead.c
$(RUNO)
sclog.o:./module/schead/sclog.c
$(RUNO)
sccsv.o:./module/schead/sccsv.c
$(RUNO)
tstring.o:./module/struct/tstring.c
$(RUNO)
cjson.o:./module/schead/cjson.c
$(RUNO)
scconf.o:./module/schead/scconf.c
$(RUNO)
tree.o:./module/struct/tree.c
$(RUNO) #删除命令
clean:
rm -rf *.i *.s *.o *.out __* log ; ls -hl
.PHONY:clean
最后展示 实现的代码
#include <schead.h>
#include <sccsv.h>
#include <sclog.h>
#include <tstring.h> //从文件中读取 csv文件内容
char* __get_csv(FILE* txt, int* prl, int* pcl)
{
int c, n;
int cl = , rl = ;
TSTRING_CREATE(ts);
while((c=fgetc(txt))!=EOF){
if('"' == c){ //处理这里数据
while((c=fgetc(txt))!=EOF){
if('"' == c) {
if((n=fgetc(txt)) == EOF) { //判断下一个字符
SL_WARNING("The CSV file is invalid one!");
free(ts.str);
return NULL;
}
if(n != '"'){ //有效字符再次压入栈
ungetc(n, txt);
break;
}
}
//都是合法字符 保存起来
if (_RT_OK != tstring_append(&ts, c)) {
free(ts.str);
return NULL;
}
}
//继续判断,只有是c == '"' 才会下来,否则都是错的
if('"' != c){
SL_WARNING("The CSV file is invalid two!");
free(ts.str);
return NULL;
}
}
else if(',' == c){
if (_RT_OK != tstring_append(&ts, '\0')) {
free(ts.str);
return NULL;
}
++cl;
}
else if('\r' == c)
continue;
else if('\n' == c){
if (_RT_OK != tstring_append(&ts, '\0')) {
free(ts.str);
return NULL;
}
++cl;
++rl;
}
else {//其它所有情况只添加数据就可以了
if (_RT_OK != tstring_append(&ts, c)) {
free(ts.str);
return NULL;
}
}
} if(cl % rl){ //检测 , 号是个数是否正常
SL_WARNING("now csv file is illegal! need check!");
return NULL;
} // 返回最终内容
*prl = rl;
*pcl = cl;
return ts.str;
} // 将 __get_csv 得到的数据重新构建返回, 执行这个函数认为语法检测都正确了
sccsv_t __get_csv_new(const char* cstr, int rl, int cl)
{
int i = ;
sccsv_t csv = malloc(sizeof(struct sccsv) + sizeof(char*)*cl);
if(NULL == csv){
SL_FATAL("malloc is error one !");
return NULL;
} // 这里开始构建内容了
csv->rlen = rl;
csv->clen = cl / rl;
do {
csv->data[i] = cstr;
while(*cstr++) //找到下一个位置处
;
}while(++i<cl); return csv;
} /*
* 从文件中构建csv对象, 最后需要调用 sccsv_die 释放
* path : csv文件内容
* : 返回构建好的 sccsv_t 对象
*/
sccsv_t
sccsv_new(const char* path)
{
FILE* txt;
char* cstr;
int rl, cl; DEBUG_CODE({
if(!path || !*path){
SL_WARNING("params is check !path || !*path .");
return NULL;
}
});
// 打开文件内容
if((txt=fopen(path, "r")) == NULL){
SL_WARNING("fopen %s r is error!", path);
return NULL;
}
// 如果解析 csv 文件内容失败直接返回
cstr = __get_csv(txt, &rl, &cl);
fclose(txt); // 返回最终结果
return cstr ? __get_csv_new(cstr, rl, cl) : NULL;
} /*
* 释放由sccsv_new构建的对象
* pcsv : 由sccsv_new 返回对象
*/
void
sccsv_die(sccsv_t* pcsv)
{
if (pcsv && *pcsv) { // 这里 开始释放
free(*pcsv);
*pcsv = NULL;
}
} /*
* 获取某个位置的对象内容
* csv : sccsv_t 对象, new返回的
* ri : 查找的行索引 [0, csv->rlen)
* ci : 查找的列索引 [0, csv->clen)
* : 返回这一项中内容,后面可以用 atoi, atof, str_dup 等处理了...
*/
inline const char*
sccsv_get(sccsv_t csv, int ri, int ci)
{
DEBUG_CODE({
if(!csv || ri< || ri>=csv->rlen || ci< || ci >= csv->clen){
SL_WARNING("params is csv:%p, ri:%d, ci:%d.", csv, ri, ci);
return NULL;
}
});
// 返回最终结果
return csv->data[ri*csv->clen + ci];
}
到这里原本要结束了,但是再扯一点,上面采用的内存模型是整体内存模型, 一共分配两次,一次为了所有字符.一次
为了保存分割串内容. 可以细细品味上面 new那段代码,还是很有意思的.
到这里简单的基础库的各个细节都已经实现完毕. 下一次会详细介绍怎么使用这个框架.再试试githup. 实在
不行再采用菜一点的做法.
后记
错误是难免的,欢迎吐槽.... 以科幻的图结束吧 -------------------------------未来是不可知的---------------------------------

C 封装一个csv 解析库的更多相关文章
- .NET Core中的CSV解析库
感谢 本篇首先特别感谢从此启程兄的<.NetCore外国一些高质量博客分享>, 发现很多国外的.NET Core技术博客资源, 我会不定期从中选择一些有意思的文章翻译总结一下. .NET ...
- [js高手之路] 跟GhostWu一起封装一个字符串工具库-架构篇(1)
所谓字符串工具库就是利用javascript面向对象的知识封装一个常用的字符串处理方法库,首先给这个库起个名字,好吧就叫ghostwu.js. 看下ghostwu.js的整体架构: ; (functi ...
- 开源一个CSV解析器(附设计过程 )
在ExcelReport支持csv的开发过程中,需要一个NETStandard的csv解析器.在nuget上找了几个试用,但都不太适合. 于是,便有了:AxinLib.IO.CSV. 先看看怎么用: ...
- 如何优雅的封装一个DOM事件库
1.DOM0级事件和DOM2级事件 DOM 0级事件是元素内的一个私有属性:div.onclick = function () {},对一个私有属性赋值(在该事件上绑定一个方法).由此可知DOM 0级 ...
- [js高手之路] 跟GhostWu一起封装一个字符串工具库-扩展trim,trimLeft,trimRight方法(2)
我们接着上一篇的继续,在上一篇我们完成了工具库的架构,本文扩展字符串去空格的方法, 一共有3个 1,trimLeft: 去除字符串左边的空格 2,trimRight: 去除字符串右边的空格 3,tri ...
- [js高手之路] 跟GhostWu一起封装一个字符串工具库-扩展字符串位置方法(4)
本文,我们接着之前的框架继续扩展,这次扩展了一共有5个与字符串位置相关的方法 between( left, right ) 返回两个字符串之间的内容, 如果第二个参数没有传递,返回的是找到的第一个参数 ...
- [js高手之路] 跟GhostWu一起封装一个字符串工具库-扩展camelize与dasherize方法(3)
在此之前,我们已经完成了4个方法: trimLeft, trimRight, trim, capitalize 本文,我们扩展驼峰式与下划线转化这两个对称的方法 camelize: 把空格,下划线,中 ...
- 一个非常棒的Go-Json解析库
json是一种数据格式,经常被用作数据交换,页面展示,序列化等场景,基本每种语言都有对应的json解析框架,Go语言也不例外,并且内置了json库,基本能够满足一些普通开发场景,但有些复杂场景下就不太 ...
- C 封装一个简单二叉树基库
引文 今天分享一个喜欢佩服的伟人,应该算人类文明极大突破者.收藏过一张纸币类型如下 那我们继续科普一段关于他的简介 '高斯有些孤傲,但令人惊奇的是,他春风得意地度过了中产阶级的一生,而 没有遭受到冷 ...
随机推荐
- 一点点seo
Search Engine Optimization(搜索引擎优化 ),是较为流行的网络营销方式. 主要目的是增加特定关键字的曝光率.有站外SEO和站内SEO.通过了解各类搜索引擎如何抓取互联网页面. ...
- 关于Windows平台下应用程序加载DLL模块的问题.
本文将讨论以下问题: (1)Windows可执行程序会从哪些目录下加载DLL. (2)如何将可执行使用的DLL放置到统一的目录下,而不是与EXE同一目录. (3)可执行程序加载了不该加载的DLL. ( ...
- 解决ScrollView下嵌套ListView、GridView显示不全的问题
/** * 自定义gridview,解决ListView中嵌套gridview显示不正常的问题(1行半) * @author wangyx * @version 1.0.0 2012-9-14 */ ...
- WebApi简单使用
一.建立一个WebApi项目 WebApi项目的文件和MVC的基本项目内容差不多,都有Models View Controller等,区别在于WebApi的控制器继承的不是Controller类,而是 ...
- [前端 3]纯Js制作俄罗斯方块游戏
导读:在别人文章里看到了,然后写了一遍.结果出错了,然后调出来了,然后理解了一下,加了点注释,有一些想法.忘了在 哪一篇上面看的了,就贴不出来链接地址.原谅.呃,真没自己的东西,权当练打字了吧.其实, ...
- 【HTML/XML 12】URI、URN、URL的联系和区别
导读:在学习XML的时候,书中有很多个地方都提到URL等几个概念,再之前做项目的时候,重定向或是转发时,也用到了这个URL,在学习Ajax时,ajax破坏了统一资源定位(URN)都或多或少的接触到了这 ...
- 2016-03-24:Windows内存泄露分析工具
参考资料 100%正确的内存泄漏分析工具 ------ tMemMonitor (TMM)
- 【MySQL】MySQL索引背后的之使用策略及优化【转】
转自:http://database.ctocio.com.cn/353/11664853.shtml 另外很不错的对于索引及索引优化的文章: http://www.cnblogs.com/magia ...
- 对于不是特别擅长Photoshop的人来说,熟悉和运用Photoshop工具提供的各类便捷的快捷键,是有帮助的。
应用程序菜单快捷键之文件 应用程序菜单快捷键之编辑 应用程序菜单快捷键之图像图层 应用程序菜单快捷键 应用程序菜单快捷键之视图 Ctrl + H 取消参考线 调板菜单 ...
- WP8_ListBox的用法
在Windows Phone 7 Tips (5) 中曾经提到,在Windows Phone 7 中页面的布局一般分为:Panoramic.Pivot.List和Full Screen.而通常List ...