程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C 簡單處理excel 轉成 json,exceljson

C 簡單處理excel 轉成 json,exceljson

編輯:關於C語言

C 簡單處理excel 轉成 json,exceljson


引言

  工作中常需要處理excel轉json問題. 希望這篇博文能簡單描述這個問題.並提供一種解決
思路.提升感悟.

  今天我們處理的事就是為了把 xlsm => json. 一種方式是. 去 google 在 stackover上搜

c readxlsm/readxls 庫. 也可以解決. 但是跨平台需要配置. 這裡介紹一種有意思的方式. 來處理read.

首先我們的目標文件是

生成的最後內容希望是

 

 

前言

  這裡會扯一點C能夠處理xlsm, 但是這些高級文件格式還是上層語言處理的爽一點. C去處理真的是屠龍刀殺泥鳅.

心累.但是本文一定會處理的哪怕再復雜. 就是要倚天劍烤雞翅.

首先來點開胃小菜. C 如何生成一個 下面的demo.xlsm

處理代碼 demo.c

#include <stdio.h>

/* 生成一個 xlsm 文件
 *  
 * demo.xlsm 
 * hello    world
 * begin    end
 *             加油    新的開始
 */
int main(int argc, char* argv[]) {
    
    FILE* xlsm = fopen("demo.xlsm", "wb");
    if(xlsm == NULL) {
        fprintf(stderr, "fopen demo.xlsm is error\n");
        return -1;
    }

    // 處理輸出
    fprintf(xlsm,"hello\tworld\n");
    fprintf(xlsm,"begin\tend\n");
    fprintf(xlsm,"\t加油\t新的開始\n");

    fclose(xlsm);

    puts("demo.xlsm 生成成功!");
    return 0;
}

是不是很有意思. 其實生成的本質, 我們用 Notepad++ 打開 demo.xlsm

 其實是我們使用的 execel處理工具為我們解析成了excel格式. 也是一種方式吧. 歡迎嘗試. 這裡扯一點.

我一直使用WPS, 也很喜歡WPS. 但是覺得WPS適合個人閱讀. 對於工作處理特別是VBA 感覺不好.  關於工作

中 excel => json都是內部VBA 代碼在 Ctrl + S的時候執行的. 最近想優化一下公司的 vba代碼,但是自己用的

是wps 有心無力. (wps 處理文件還是有點慢, 宏和vba專業功能支持的蛋疼.)

推薦逼格高, 還是用微軟的 office最新版專業(旗艦)破解版吧. 畢竟Microsoft 是通過 office 騰飛的. 值得用破解版. 

下面我們要到正題了.

 

本文需要用的輔助文件. 後面源碼中處理的函數調用實現都在下面文件找找到定義.

schead.h

#ifndef _H_SCHEAD #define _H_SCHEAD #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <errno.h> #include <string.h> #include <time.h> #include <stdint.h> #include <stddef.h> /* * 1.0 錯誤定義宏 用於判斷返回值狀態的狀態碼 _RF表示返回標志 * 使用舉例 : int flag = scconf_get("pursue"); if(flag != _RT_OK){ sclog_error("get config %s error! flag = %d.", "pursue", flag); exit(EXIT_FAILURE); } * 這裡是內部 使用的通用返回值 標志 */ #define _RT_OK (0) //結果正確的返回宏 #define _RT_EB (-1) //錯誤基類型,所有錯誤都可用它,在不清楚的情況下 #define _RT_EP (-2) //參數錯誤 #define _RT_EM (-3) //內存分配錯誤 #define _RT_EC (-4) //文件已經讀取完畢或表示鏈接關閉 #define _RT_EF (-5) //文件打開失敗 /* * 1.1 定義一些 通用的函數指針幫助,主要用於基庫的封裝中 * 有構造函數, 釋放函數, 比較函數等 */ typedef void* (*pnew_f)(); typedef void (*vdel_f)(void* node); // icmp_f 最好 是 int cmp(const void* ln,const void* rn); 標准結構 typedef int (*icmp_f)(); /* * c 如果是空白字符返回 true, 否則返回false * c : 必須是 int 值,最好是 char 范圍 */ #define sh_isspace(c) \ ((c==' ')||(c>='\t'&&c<='\r')) /* * 2.0 如果定義了 __GNUC__ 就假定是 使用gcc 編譯器,為Linux平台 * 否則 認為是 Window 平台,不可否認宏是丑陋的 */ #if defined(__GNUC__) //下面是依賴 Linux 實現,等待毫秒數 #include <unistd.h> #include <sys/time.h> #define SLEEPMS(m) \ usleep(m * 1000) #else // 這裡創建等待函數 以毫秒為單位 , 需要依賴操作系統實現 #include <Windows.h> #include <direct.h> // 加載多余的頭文件在 編譯階段會去掉 #define rmdir _rmdir /** * Linux sys/time.h 中獲取時間函數在Windows上一種移植實現 **tv : 返回結果包含秒數和微秒數 **tz : 包含的時區,在window上這個變量沒有用不返回 ** : 默認返回0 **/ extern int gettimeofday(struct timeval* tv, void* tz); //為了解決 不通用功能 #define localtime_r(t, tm) localtime_s(tm, t) #define SLEEPMS(m) \ Sleep(m) #endif /*__GNUC__ 跨平台的代碼都很丑陋 */ //3.0 浮點數據判斷宏幫助, __開頭表示不希望你使用的宏 #define __DIFF(x, y) ((x)-(y)) //兩個表達式做差宏 #define __IF_X(x, z) ((x)<z&&(x)>-z) //判斷宏,z必須是宏常量 #define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判斷x和y是否在誤差范圍內相等 //3.1 float判斷定義的宏 #define _FLOAT_ZERO (0.000001f) //float 0的誤差判斷值 #define EQ_FLOAT_ZERO(x) __IF_X(x,_FLOAT_ZERO) //float 判斷x是否為零是返回true #define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判斷表達式x與y是否相等 //3.2 double判斷定義的宏 #define _DOUBLE_ZERO (0.000000000001) //double 0誤差判斷值 #define EQ_DOUBLE_ZERO(x) __IF_X(x,_DOUBLE_ZERO) //double 判斷x是否為零是返回true #define EQ_DOUBLE(x,y) EQ(x, y, _DOUBLE_ZERO) //判斷表達式x與y是否相等 //4.0 控制台打印錯誤信息, fmt必須是雙引號括起來的宏 #ifndef CERR #define CERR(fmt, ...) \ fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\ __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__) #endif/* !CERR */ //4.1 控制台打印錯誤信息並退出, t同樣fmt必須是 ""括起來的字符串常量 #ifndef CERR_EXIT #define CERR_EXIT(fmt,...) \ CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) #endif/* !ERR */ #ifndef IF_CERR /* *4.2 if 的 代碼檢測 * * 舉例: * IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!"); * 遇到問題打印日志直接退出,可以認為是一種簡單模板 * code : 要檢測的代碼 * fmt : 必須是""括起來的字符串宏 * ... : 後面的參數,參照printf */ #define IF_CERR(code, fmt, ...) \ if((code) < 0) \ CERR_EXIT(fmt, ##__VA_ARGS__) #endif //!IF_CERR #ifndef IF_CHECK /* * 是上面IF_CERR 的簡化版很好用 */ #define IF_CHECK(code) \ if((code) < 0) \ CERR_EXIT(#code) #endif // !IF_CHECK //5.0 獲取數組長度,只能是數組類型或""字符串常量,後者包含'\0' #ifndef LEN #define LEN(arr) \ (sizeof(arr)/sizeof(*(arr))) #endif/* !ARRLEN */ //6.0 程序清空屏幕函數 #ifndef CONSOLE_CLEAR #ifndef _WIN32 #define CONSOLE_CLEAR() \ system("printf '\ec'") #else #define CONSOLE_CLEAR() \ system("cls") #endif/* _WIN32 */ #endif /*!CONSOLE_CLEAR*/ //7.0 置空操作 #ifndef BZERO //v必須是個變量 #define BZERO(v) \ memset(&v,0,sizeof(v)) #endif/* !BZERO */ //9.0 scanf 健壯的 #ifndef SAFETY_SCANF #define SAFETY_SCANF(scanf_code,...) \ while(printf(__VA_ARGS__),scanf_code){\ while(getchar()!='\n');\ puts("輸入出錯,請按照提示重新操作!");\ }\ while(getchar()!='\n') #endif /*!SAFETY_SCANF*/ //10.0 簡單的time幫助宏 #ifndef TIME_PRINT #define TIME_PRINT(code) {\ clock_t __st,__et;\ __st=clock();\ code\ __et=clock();\ printf("當前代碼塊運行時間是:%lf秒\n",(0.0+__et-__st)/CLOCKS_PER_SEC);\ } #endif /*!TIME_PRINT*/ /* * 10.1 這裡是一個 在 DEBUG 模式下的測試宏 * * 用法 : * DEBUG_CODE({ * puts("debug start..."); * }); */ #ifndef DEBUG_CODE # ifdef _DEBUG # define DEBUG_CODE(code) code # else # define DEBUG_CODE(code) # endif // ! _DEBUG #endif // !DEBUG_CODE //11.0 等待的宏 是個單線程沒有加鎖 #define _STR_PAUSEMSG "請按任意鍵繼續. . ." extern void sh_pause(void); #ifndef INIT_PAUSE # ifdef _DEBUG # define INIT_PAUSE() atexit(sh_pause) # else # define INIT_PAUSE() (void)316 /* 別說了,都重新開始吧 */ # endif #endif/* !INIT_PAUSE */ //12.0 判斷是大端序還是小端序,大端序返回true extern bool sh_isbig(void); /** * sh_free - 簡單的釋放內存函數,對free再封裝了一下 **可以避免野指針 **pobj:指向待釋放內存的指針(void*) **/ extern void sh_free(void** pobj); /** * 獲取 當前時間串,並塞入tstr中長度並返回 ** 使用舉例 char tstr[64]; sh_times(tstr, LEN(tstr)); puts(tstr); **tstr : 保存最後生成的最後串 **len : tstr數組的長度 ** : 返回tstr首地址 **/ extern int sh_times(char tstr[], int len); /* * 比較兩個結構體棧上內容是否相等,相等返回true,不等返回false * a : 第一個結構體值 * b : 第二個結構體值 * : 相等返回true, 否則false */ #define STRUCTCMP(a, b) \ (!memcmp(&a, &b, sizeof(a))) #endif/* ! _H_SCHEAD */ View Code

schead.c

#include <schead.h> //簡單通用的等待函數 void sh_pause(void) { rewind(stdin); printf(_STR_PAUSEMSG); getchar(); } //12.0 判斷是大端序還是小端序,大端序返回true bool sh_isbig(void) { static union { unsigned short _s; unsigned char _c; } __u = { 1 }; return __u._c == 0; } /** * sh_free - 簡單的釋放內存函數,對free再封裝了一下 **可以避免野指針 **@pobj:指向待釋放內存的指針(void*) **/ void sh_free(void** pobj) { if (pobj == NULL || *pobj == NULL) return; free(*pobj); *pobj = NULL; } #if defined(_MSC_VER) /** * Linux sys/time.h 中獲取時間函數在Windows上一種移植實現 **tv : 返回結果包含秒數和微秒數 **tz : 包含的時區,在window上這個變量沒有用不返回 ** : 默認返回0 **/ int gettimeofday(struct timeval* tv, void* tz) { time_t clock; struct tm tm; SYSTEMTIME wtm; GetLocalTime(&wtm); tm.tm_year = wtm.wYear - 1900; tm.tm_mon = wtm.wMonth - 1; //window的計數更好寫 tm.tm_mday = wtm.wDay; tm.tm_hour = wtm.wHour; tm.tm_min = wtm.wMinute; tm.tm_sec = wtm.wSecond; tm.tm_isdst = -1; //不考慮夏令時 clock = mktime(&tm); tv->tv_sec = (long)clock; //32位使用,接口已經老了 tv->tv_usec = wtm.wMilliseconds * 1000; return _RT_OK; } #endif /** * 獲取 當前時間串,並塞入tstr中C長度並返回 ** 使用舉例 char tstr[64]; puts(gettimes(tstr, LEN(tstr))); **tstr : 保存最後生成的最後串 **len : tstr數組的長度 ** : 返回tstr首地址 **/ int sh_times(char tstr[], int len) { struct tm st; time_t t = time(NULL); localtime_r(&t, &st); return (int)strftime(tstr, len, "%F %X", &st); } View Code

sclog.h

#ifndef _H_SCLOG #define _H_SCLOG //-------------------------------------------------------------------------------------------| // 第一部分 共用的參數宏 //-------------------------------------------------------------------------------------------| // //關於日志切分,需要用第三方插件例如crontab , 或者下次我自己寫一個監測程序. #define _INT_LITTLE (64) //保存時間或IP長度 #define _INT_LOG (1024<<3) //最多8k日志 #define _STR_SCLOG_PATH "log" //日志相對路徑目錄,如果不需要需要配置成"" #define _STR_SCLOG_LOG "sc.log" //普通log日志 DEBUG,INFO,NOTICE,WARNING,FATAL都會輸出 #define _STR_SCLOG_WFLOG "sc.log.wf" //級別比較高的日志輸出 FATAL和WARNING /** * fstr : 為標識串 例如 _STR_SCLOG_FATAL, 必須是雙引號括起來的串 ** ** 拼接一個 printf 輸出格式串 **/ #define SCLOG_PUTS(fstr) \ "%s][" fstr "][%s:%d:%s][logid:%u][reqip:%s][mod:%s]" #define _STR_SCLOG_FATAL "FATAL" //錯誤,後端使用 #define _STR_SCLOG_WARNING "WARNING" //警告,前端使用錯誤,用這個 #define _STR_SCLOG_NOTICE "NOTICE" //系統使用,一般標記一條請求完成,使用這個日志 #define _STR_SCLOG_INFO "INFO" //普通的日志打印 #define _STR_SCLOG_TRACE "TRACE" #define _STR_SCLOG_DEBUG "DEBUG" //測試用的日志打印,在發布版這些日志會被清除掉 /** * fstr : 只能是 _STR_SCLOG_* 開頭的宏 ** fmt : 必須是""括起來的宏.單獨輸出的格式宏 ** ... : 對映fmt參數集 ** ** 拼接這裡使用的宏,為sl_printf 打造一個模板,這裡存在一個坑,在Window \n表示 CRLF, Unix就是LF **/ #define SCLOG_PRINTF(fstr, fmt, ...) \ sl_printf(SCLOG_PUTS(fstr) fmt "\n", sl_get_times(), __FILE__, __LINE__, __func__, \ sl_get_logid(), sl_get_reqip(), sl_get_mod(), ##__VA_ARGS__) /** * FATAL... 日志打印宏 ** fmt : 輸出的格式串,需要""包裹起來 ** ... : 後面的參數,服務於fmt **/ #define SL_FATAL(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_FATAL, fmt, ##__VA_ARGS__) #define SL_WARNING(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_WARNING, fmt, ##__VA_ARGS__) #define SL_NOTICE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_NOTICE, fmt, ##__VA_ARGS__) #define SL_INFO(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_INFO, fmt, ##__VA_ARGS__) // 發布狀態下,關閉SL_DEBUG 宏,需要重新編譯,沒有改成運行時的判斷,這個框架主要圍繞單機部分多服務器 #if defined(_DEBUG) # define SL_TRACE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_TRACE, fmt, ##__VA_ARGS__) # define SL_DEBUG(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_DEBUG, fmt, ##__VA_ARGS__) #else # define SL_TRACE(fmt, ...) (void)0x123 /* 人生難道就是123*/ # define SL_DEBUG(fmt, ...) (void)0xa91 /* 愛過哎 */ #endif //-------------------------------------------------------------------------------------------| // 第二部分 對日志信息體操作的get和set,這裡隱藏了信息體的實現 //-------------------------------------------------------------------------------------------| /** * 線程的私有數據初始化 ** ** mod : 當前線程名稱 ** reqip : 請求的ip ** return : _RT_OK 表示正常,_RF_EM內存分配錯誤 **/ extern int sl_pecific_init(const char* mod, const char* reqip); /** * 重新設置線程計時時間 ** 正常返回 _RT_OK, _RT_EM表示內存沒有分配 **/ int sl_set_timev(void); /** * 獲取日志信息體的唯一的logid **/ unsigned sl_get_logid(void); /** * 獲取日志信息體的請求ip串,返回NULL表示沒有初始化 **/ const char* sl_get_reqip(void); /** * 獲取日志信息體的時間串,返回NULL表示沒有初始化 **/ const char* sl_get_times(void); /** * 獲取日志信息體的名稱,返回NULL表示沒有初始化 **/ const char* sl_get_mod(void); //-------------------------------------------------------------------------------------------| // 第三部分 對日志系統具體的輸出輸入接口部分 //-------------------------------------------------------------------------------------------| /** * 日志系統首次使用初始化,找對對映日志文件路徑,創建指定路徑 **/ extern void sl_start(void); /** * 這個函數不希望你使用,是一個內部限定死的日志輸出內容.推薦使用相應的宏 **打印相應級別的日志到對映的文件中. ** ** format : 必須是""號括起來的宏,開頭必須是 [FALTAL:%s]後端錯誤 ** [WARNING:%s]前端錯誤, [NOTICE:%s]系統使用, [INFO:%s]普通信息, ** [DEBUG:%s] 開發測試用 ** ** return : 返回輸出內容長度 **/ int sl_printf(const char* format, ...); #endif // !_H_SCLOG View Code

sclog.c

#include <sclog.h> #include <schead.h> #include <scatom.h> #include <pthread.h> #include <stdarg.h> //-------------------------------------------------------------------------------------------| // 第二部分 對日志信息體操作的get和set,這裡隱藏了信息體的實現 //-------------------------------------------------------------------------------------------| //線程私有數據 __lkey, __lonce為了__lkey能夠正常初始化 static pthread_key_t __lkey; static pthread_once_t __lonce = PTHREAD_ONCE_INIT; static unsigned __logid = 0; //默認的全局logid,唯一標識 //內部簡單的釋放函數,服務於pthread_key_create 防止線程資源洩露 static void __slinfo_destroy(void* slinfo) { //printf("pthread 0x%p:0x%p destroy!\n", pthread_self().p, slinfo); free(slinfo); } static void __gkey(void) { pthread_key_create(&__lkey, __slinfo_destroy); } struct slinfo { unsigned logid; //請求的logid,唯一id char reqip[_INT_LITTLE]; //請求方ip char times[_INT_LITTLE]; //當前時間串 struct timeval timev; //處理時間,保存值,統一用毫秒 char mod[_INT_LITTLE]; //當前線程的模塊名稱,不能超過_INT_LITTLE - 1 }; /** * 線程的私有數據初始化 ** ** mod : 當前線程名稱 ** reqip : 請求的ip ** return : _RT_OK 表示正常,_RF_EM內存分配錯誤 **/ int sl_pecific_init(const char* mod, const char* reqip) { struct slinfo* pl; //保證 __gkey只被執行一次 pthread_once(&__lonce, __gkey); if((pl = pthread_getspecific(__lkey)) == NULL){ //重新構建 if ((pl = malloc(sizeof(struct slinfo))) == NULL) return _RT_EM; //printf("pthread 0x%p:0x%p create!\n", pthread_self().p,pl); } gettimeofday(&pl->timev, NULL); pl->logid = ATOM_ADD_FETCH(__logid, 1); //原子自增 strcpy(pl->mod, mod); //復制一些數據 strcpy(pl->reqip, reqip); //設置私有變量 pthread_setspecific(__lkey, pl); return _RT_OK; } /** * 重新設置線程計時時間 ** 正常返回 _RT_OK, _RT_EM表示內存沒有分配 **/ int sl_set_timev(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) return _RT_EM; gettimeofday(&pl->timev, NULL); return _RT_OK; } /** * 獲取日志信息體的唯一的logid **/ unsigned sl_get_logid(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回0表示沒有找見 return 0u; return pl->logid; } /** * 獲取日志信息體的請求ip串,返回NULL表示沒有初始化 **/ const char* sl_get_reqip(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回NULL表示沒有找見 return NULL; return pl->reqip; } /** * 獲取日志信息體的時間串,返回NULL表示沒有初始化 **/ const char* sl_get_times(void) { struct timeval et; //記錄時間 unsigned td; struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回NULL表示沒有找見 return NULL; gettimeofday(&et, NULL); //同一用微秒記 td = 1000000 * (et.tv_sec - pl->timev.tv_sec) + et.tv_usec - pl->timev.tv_usec; snprintf(pl->times, LEN(pl->times), "%u", td); return pl->times; } /** * 獲取日志信息體的名稱,返回NULL表示沒有初始化 **/ const char* sl_get_mod(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回NULL表示沒有找見 return NULL; return pl->mod; } //-------------------------------------------------------------------------------------------| // 第三部分 對日志系統具體的輸出輸入接口部分 //-------------------------------------------------------------------------------------------| //錯誤重定向宏 具體應用 於 "mkdir -p \"" _STR_SCLOG_PATH "\" >" _STR_TOOUT " 2>" _STR_TOERR #define _STR_TOOUT "__out__" #define _STR_TOERR "__err__" #define _STR_LOGID "__lid__" //保存logid,持久化 static struct { //內部用的私有變量 FILE* log; FILE* wf; bool isdir; //標志是否創建了目錄 } __slmain; /** * 日志關閉時候執行,這個接口,關閉打開的文件句柄 **/ static void __sl_end(void) { FILE* lid; void* pl; // 在簡單地方多做安全操作值得,在核心地方用算法優化的才能穩固 if (!__slmain.isdir) return; //重置當前系統打開文件結構體 fclose(__slmain.log); fclose(__slmain.wf); BZERO(__slmain); //寫入文件 lid = fopen(_STR_LOGID, "w"); if (NULL != lid) { fprintf(lid, "%u", __logid); fclose(lid); } //主動釋放私有變量,其實主進程 相當於一個線程是不合理的!還是不同的生存周期的 pl = pthread_getspecific(__lkey); __slinfo_destroy(pl); pthread_setspecific(__lkey, NULL); } /** * 日志系統首次使用初始化,找對對映日志文件路徑,創建指定路徑 **/ void sl_start(void) { FILE *lid; //單例只執行一次 if (!__slmain.isdir) { __slmain.isdir = true; //先多級創建目錄,簡易不借助宏實現跨平台,system返回值是很復雜,默認成功! system("mkdir -p \"" _STR_SCLOG_PATH "\" >" _STR_TOOUT " 2>" _STR_TOERR); rmdir("-p"); remove(_STR_TOOUT); remove(_STR_TOERR); } if (NULL == __slmain.log) { __slmain.log = fopen(_STR_SCLOG_PATH "/" _STR_SCLOG_LOG, "a+"); if (NULL == __slmain.log) CERR_EXIT("__slmain.log fopen %s error!", _STR_SCLOG_LOG); } //繼續打開 wf 文件 if (NULL == __slmain.wf) { __slmain.wf = fopen(_STR_SCLOG_PATH "/" _STR_SCLOG_WFLOG, "a+"); if (NULL == __slmain.wf) { fclose(__slmain.log); //其實這都沒有必要,圖個心安 CERR_EXIT("__slmain.log fopen %s error!", _STR_SCLOG_WFLOG); } } //讀取文件內容 if ((lid = fopen(_STR_LOGID, "r")) != NULL) { //讀取文件內容,持久化 fscanf(lid, "%u", &__logid); } //這裡可以單獨開啟一個線程或進程,處理日志整理但是 這個模塊可以讓運維做,按照規則搞 sl_pecific_init("main thread","0.0.0.0"); //注冊退出操作 atexit(__sl_end); } int sl_printf(const char* format, ...) { char tstr[_INT_LITTLE];// [%s] => [2016-01-08 23:59:59] int len; va_list ap; char logs[_INT_LOG]; //這個不是一個好的設計,最新c 中支持 int a[n]; if (!__slmain.isdir) { CERR("%s fopen %s | %s error!",_STR_SCLOG_PATH, _STR_SCLOG_LOG, _STR_SCLOG_WFLOG); return _RT_EF; } //初始化參數 sh_times(tstr, _INT_LITTLE - 1); len = snprintf(logs, LEN(logs), "[%s ", tstr); va_start(ap, format); vsnprintf(logs + len, LEN(logs) - len, format, ap); va_end(ap); // 寫普通文件 log fputs(logs, __slmain.log); //把鎖機制去掉了,fputs就是線程安全的 // 寫警告文件 wf if (format[4] == 'F' || format[4] == 'W') { //當為FATAL或WARNING需要些寫入到警告文件中 fputs(logs, __slmain.wf); } return _RT_OK; } View Code

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 View Code

sccsv.c

#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 = 0, rl = 0; 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 = 0; 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<0 || ri>=csv->rlen || ci<0 || 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]; } View Code

tstring.h

#ifndef _H_TSTRING #define _H_TSTRING #include <schead.h> //------------------------------------------------簡單字符串輔助操作---------------------------------- /* * 主要采用jshash 返回計算後的hash值 * 不沖突率在 80% 左右還可以, 不要傳入NULL */ extern unsigned str_hash(const char* str); /* * 這是個不區分大小寫的比較函數 * ls : 左邊比較字符串 * rs : 右邊比較字符串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ extern int str_icmp(const char* ls, const char* rs); /* * 這個代碼是 對 strdup 的再實現, 調用之後需要free * str : 待復制的源碼內容 * : 返回 復制後的串內容 */ extern char* str_dup(const char* str); //------------------------------------------------簡單文本字符串輔助操作---------------------------------- #ifndef _STRUCT_TSTRING #define _STRUCT_TSTRING //簡單字符串結構,並定義文本字符串類型tstring struct tstring { char* str; //字符串實際保存的內容 int len; //當前字符串大小 int size; //字符池大小 }; typedef struct tstring* tstring; #endif // !_STRUCT_TSTRING //文本串棧上創建內容,不想用那些技巧了,就這樣吧 #define TSTRING_CREATE(var) \ struct tstring var = { NULL, 0, 0} #define TSTRING_DESTROY(var) \ free(var.str) /* * tstring 的創建函數, 會根據str創建一個 tstring結構的字符串 * * str : 待創建的字符串 * * ret : 返回創建好的字符串,如果創建失敗返回NULL */ extern tstring tstring_create(const char* str); /* * tstring 完全銷毀函數 * tstr : 指向tsting字符串指針量的指針 */ extern void tstring_destroy(tstring* tstr); /* * 向簡單文本字符串tstr中添加 一個字符c * tstr : 簡單字符串對象 * c : 待添加的字符 * ret : 返回狀態碼 見 schead 中 _RT_EB 碼等 */ extern int tstring_append(tstring tstr, int c); /* * 向簡單文本串中添加只讀字符串 * tstr : 文本串 * str : 待添加的素材串 * ret : 返回狀態碼主要是 _RT_EP _RT_EM */ extern int tstring_appends(tstring tstr, const char* str); /* * 復制tstr中內容,得到char* 返回,需要自己free釋放 *假如你要清空tstring 字符串只需要 設置 len = 0.就可以了 * tstr : 待分配的字符串 * : 返回分配好的字符串首地址 */ extern char* tstring_mallocstr(tstring tstr); //------------------------------------------------簡單文件輔助操作---------------------------------- /* * 簡單的文件幫助類,會讀取完畢這個文件內容返回,失敗返回NULL. * 需要事後使用 tstring_destroy(&ret); 銷毀這個字符串對象 * path : 文件路徑 * ret : 返回創建好的字符串內容,返回NULL表示讀取失敗 */ extern tstring file_malloc_readend(const char* path); /* * 文件寫入,沒有好說的,會返回 _RT_EP _RT_EM _RT_OK * path : 文件路徑 * str : 待寫入的字符串 * ret : 返回寫入的結果 */ extern int file_writes(const char* path, const char* str); /* * 文件追加內容, 添加str內同 * path : 文件路徑 * str : 待追加的文件內同 * : 返回值,主要是 _RT_EP _RT_EM _RT_OK 這些狀態 */ extern int file_append(const char* path, const char* str); #endif // !_H_TSTRING View Code

tstring.c

#include <tstring.h> #include <sclog.h> /* * 主要采用jshash 返回計算後的hash值 * 不沖突率在 80% 左右還可以, 不要傳入NULL */ unsigned str_hash(const char* str) { unsigned i, h = (unsigned)strlen(str), sp = (h >> 5) + 1; unsigned char* ptr = (unsigned char*)str; for (i = h; i >= sp; i -= sp) h ^= ((h<<5) + (h>>2) + ptr[i-1]); return h ? h : 1; } /* * 這是個不區分大小寫的比較函數 * ls : 左邊比較字符串 * rs : 右邊比較字符串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ int str_icmp(const char* ls, const char* rs) { int l, r; if(!ls || !rs) return (int)(ls - rs); do { if((l=*ls++)>='a' && l<='z') l -= 'a' - 'A'; if((r=*rs++)>='a' && r<='z') r -= 'a' - 'A'; } while(l && l==r); return l-r; } /* * 這個代碼是 對 strdup 的再實現, 調用之後需要free * str : 待復制的源碼內容 * : 返回 復制後的串內容 */ char* str_dup(const char* str) { size_t len; char* nstr; DEBUG_CODE({ if (NULL == str) { SL_WARNING("check is NULL == str!!"); return NULL; } }); len = sizeof(char) * (strlen(str) + 1); if (!(nstr = malloc(len))) { SL_FATAL("malloc is error! len = %d.", len); return NULL; } // 返回最後結果 return memcpy(nstr, str, len); } //------------------------------------------------簡單文本字符串輔助操作---------------------------------- /* * tstring 的創建函數, 會根據str創建一個 tstring結構的字符串 * * str : 待創建的字符串 * * ret : 返回創建好的字符串,如果創建失敗返回NULL */ tstring tstring_create(const char* str) { tstring tstr = calloc(1, sizeof(struct tstring)); if (NULL == tstr) { SL_NOTICE("calloc is sizeof struct tstring error!"); return NULL; } tstring_appends(tstr, str); return tstr; } /* * tstring 完全銷毀函數 * tstr : 指向tsting字符串指針量的指針 */ void tstring_destroy(tstring* tstr) { if (tstr && *tstr) { //展現內容 free((*tstr)->str); free(*tstr); *tstr = NULL; } } //文本字符串創建的度量值 #define _INT_TSTRING (32) //簡單分配函數,智力一定會分配內存的, len > size的時候調用這個函數 static int __tstring_realloc(tstring tstr, int len) { int size = tstr->size; for (size = size < _INT_TSTRING ? _INT_TSTRING : size; size < len; size <<= 1) ; //分配內存 char *nstr = realloc(tstr->str, size); if (NULL == nstr) { SL_NOTICE("realloc(tstr->str:0x%p, size:%d) is error!", tstr->str, size); return _RT_EM; } tstr->str = nstr; tstr->size = size; return _RT_OK; } /* * 向簡單文本字符串tstr中添加 一個字符c * tstr : 簡單字符串對象 * c : 待添加的字符 * ret : 返回狀態碼 見 schead 中 _RT_EM 碼等 */ int tstring_append(tstring tstr, int c) { //不做安全檢查 int len = tstr->len + 2; // c + '\0' 而len只指向 字符串strlen長度 //需要進行內存分配,唯一損失 if ((len > tstr->size) && (_RT_EM == __tstring_realloc(tstr, len))) return _RT_EM; tstr->len = --len; tstr->str[len - 1] = c; tstr->str[len] = '\0'; return _RT_OK; } /* * 向簡單文本串中添加只讀字符串 * tstr : 文本串 * str : 待添加的素材串 * ret : 返回狀態碼主要是 _RT_EP _RT_EM */ int tstring_appends(tstring tstr, const char* str) { int len; if (!tstr || !str || !*str) { SL_NOTICE("check param '!tstr || !str || !*str'"); return _RT_EP; } len = tstr->len + (int)strlen(str) + 1; if ((len > tstr->size) && (_RT_EM == __tstring_realloc(tstr, len))) return _RT_EM; //這裡復制內容 strcpy(tstr->str + tstr->len, str); tstr->len = len - 1; return _RT_OK; } //------------------------------------------------簡單文件輔助操作---------------------------------- /* * 簡單的文件幫助類,會讀取完畢這個文件內容返回,失敗返回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; } /* * 文件寫入,沒有好說的,會返回 _RT_EP _RT_EM _RT_OK * path : 文件路徑 * str : 待寫入的字符串 * ret : 返回寫入的結果 */ int file_writes(const char* path, const char* str) { FILE* txt; //檢查參數問題 if (!path || !str) { SL_NOTICE("check is '!path || !str'"); return _RT_EP; } if ((txt = fopen(path, "w")) == NULL) { SL_NOTICE("fopen w path = '%s' error!", path); return _RT_EF; } //這裡寫入信息 fputs(str, txt); fclose(txt); return _RT_OK; } /* * 文件追加內容, 添加str內同 * path : 文件路徑 * str : 待追加的文件內同 * : 返回值,主要是 _RT_EP _RT_EM _RT_OK 這些狀態 */ int file_append(const char* path, const char* str) { FILE* txt; //檢查參數問題 if (!path || !str) { SL_NOTICE("check is '!path || !str'"); return _RT_EP; } if ((txt = fopen(path, "a")) == NULL) { SL_NOTICE("fopen a path = '%s' error!", path); return _RT_EF; } //這裡寫入信息 fputs(str, txt); fclose(txt); return _RT_OK; } /* * 復制tstr中內容,得到char* 返回,需要自己free釋放 * tstr : 待分配的字符串 * : 返回分配好的字符串首地址 */ char* tstring_mallocstr(tstring tstr) { char* str; if (!tstr || tstr->len <= 0) { SL_NOTICE("params is check '!tstr || tstr->len <= 0' error!"); return NULL; } if ((str = malloc(tstr->len + 1)) == NULL){ SL_NOTICE("malloc %d+1 run is error!",tstr->len); return NULL; } //下面就可以復制了,采用最快的一種方式 return memcpy(str, tstr->str, tstr->len + 1); } View Code

 

正文

那我們開始把將一個xlsm如何轉成工作中用的json文件. 再繼續重復扯一點, 最優方法是vba, 次優方法利用上層框架例如.net 通過npoi寫個批量

處理工具. 最難受的方法就是本文中采用c處理業務代碼. 好吧繼續. 首先 我們仍然使用 destiny.xlsm文件 . 另存為

destiny.csv

 最後的 保存後結果為

我們查看內部詳細文本編碼協議

最後得到這個文件. 我們處理方式是.

先讀取 這個csv 文件 通過sccsv.h 提供的接口. 拼接一個生成的json內容. 處理代碼

 test_xlsmtojson.c

#include <schead.h>
#include <sclog.h>
#include <sccsv.h>
#include <tstring.h>

// 將csv轉換成json文件輸出, 成功返回0, 錯誤見狀態碼<0
int csvtojson(const char* path);

int main(int argc, char* argv[]) {
    int i = 0, rt;

    // 簡單參數檢查
    if(argc < 2) {
        CERR("uage: %s [*.csv]", argv[0]);
        return 0;
    }
    
    // sccsv 使用了 sclog 需要啟動 sclog
    sl_start();
    // 開始解析處理
    while(++i<argc){
        rt = csvtojson(argv[i]);
        if(0 == rt) 
            printf("%s 轉換成功\n", argv[i]);
        else
            printf("%s 轉換失敗, rt = %d\n", argv[i], rt);
    }
    
    return 0;
}

// 得到生成json文件的名稱, 需要自己free
static char* _csvtojsonpath(const char* path) {
    char *tarp;
    int len = strlen(path);
    // 判斷後綴名
    if(str_icmp(path+len-4, ".csv")){
        CERR("path is %s need *.csv", path);
        return NULL;
    }
    // 這裡申請內存進行處理
    if((tarp = malloc(len+2))==NULL) {
        CERR("malloc is error!");
        return NULL;
    }
    
    // 返回最終結果
    memcpy(tarp, path, len - 3);
    // 添加json後綴名
    tarp[len-3] = 'j';
    tarp[len-2] = 's';
    tarp[len-1] = 'o';
    tarp[len] = 'n';
    tarp[len + 1] = '\0';
    return tarp;
}

// csv read -> json write
static void _csvtojson(sccsv_t csv, FILE* json) {
    // 第一行, 第一列都是不處理的
    int c, r;
    // 第二行 內容是對象中主鍵內容
    int clen = csv->clen - 1, rlen = csv->rlen - 1;
    // 先確定最優行和列
    while (rlen > 2) {
        if (*sccsv_get(csv, rlen, 1))
            break;
        --rlen;
    }
    while (clen > 1) {
        if (*sccsv_get(csv, 0, clen))
            break;
        --clen;
    }

    // 最外層是個數組
    fputs("[\n", json);
    for (r = 2; r <= rlen; ++r) {
        // 當對象處理
        fputs("\t{\n", json);

        // 輸出當前對象中內容
        for (c = 1; c <= clen; ++c) {
            fprintf(json, "\t\t\"%s\":%s", sccsv_get(csv, 1, c),sccsv_get(csv, r, c));
            fputs(c == clen ? "\n" : ",\n", json);
        }

        // 最新的json語法支持多個 ','
        fputs(r == rlen ? "\t}\n" : "\t},\n", json);
    }

    fputs("]", json);
}

// 將csv轉換成json文件輸出, 成功返回0, 錯誤見狀態碼<0
int 
csvtojson(const char* path) {
    char* tarp;
    FILE* json;
    sccsv_t csv;
    
    if(!path || !*path) {
        CERR("path is null!");
        return _RT_EP;
    }
    
    // 繼續判斷後綴名
    if((tarp = _csvtojsonpath(path)) == NULL ) {
        CERR("path = %s is error!", path);
        return _RT_EP;
    }
    // 這裡開始打開文件, 並判斷
    if((csv = sccsv_new(path)) == NULL) {
        free(tarp);
        CERR("sccsv_new %s is error!", path);
        return _RT_EF;
    }
    if((json = fopen(tarp, "wb")) == NULL ) {
        sccsv_die(&csv);
        free(tarp);
        CERR("fopen %s wb is error!", tarp);
        return _RT_EF;
    }
    
    // 到這裡一切前戲都好了開始轉換了
    _csvtojson(csv, json);
    
    fclose(json);
    sccsv_die(&csv);
    free(tarp);
    return _RT_OK;
}

其實還是比較短的. 分析如下

static char* _csvtojsonpath(const char* path)

功能是將 *csv 路徑字符串 編程 *.json路徑字符串,保存在堆上,需要後續 free.

static void _csvtojson(sccsv_t csv, FILE* json)

上面函數將解析好的 csv結構保存輸出到json中.

對於 sccsv.h 的具體使用可以參見

C 封裝一個csv 解析庫      http://www.cnblogs.com/life2refuel/p/5265167.html

對於開頭的兩個 while是為了確定最終范圍的. 拼接最終結果. 是不是很簡單. 從上面看出, 實際處理代碼其實很少,主要是

對參數檢測健壯性代碼占了很多. 其它都是業務轉換代碼. 核心還是解析csv文件結構和json個數的拼接.

運行結果如下

最終生成的 destiny.json文件結構圖

到這裡話. 基本的處理方式方法都已經介紹完畢了. 總結C處理業務功能事有點不合時宜了. 上層語言還是值得我們深究的.

有興趣的可以用上層語言寫一個工具來幫我們批量處理excel文件生成json. 

再扯一點. json 文件相比xml 文件, 首先內存上小了很多. 處理速度快了. 但是json文件的性能浪費在

重復的鍵值. 浪費空間.

再再擴展一下下. 怎麼處理優化呢. 一種空間換時間, 轉成內存文件避免動態解析生成. 就是承認浪費. 但是一次加載解析到內存中.

還有一種 將 對象編程數組去掉鍵值但是這樣可讀性很不好.

其它,希望有更好的協議出現. 或者誕生在博客園...

 

後記

  錯誤是難免的, 希望這些對問題的分析和深究對你有幫助. 激發大家的思考. 有興趣的可以寫寫更好的工具. 交流分享.

拜~~. 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved