引言

  工作中常供给处理excel转json难点.
希望那篇博文能差不多描述那几个难点.并提供一种缓解
思路.进步感悟.

  前几天大家处理的事正是为着把 xlsm
=> json. 一种方法是. 去 google 在 stackover上搜

c readxlsm/readxls 库. 也可以消除. 可是跨平台须求配置. 那里介绍一种有意思的方式. 来处理read.

先是大家的目的文件是

json 1

转变的末梢内容希望是

json 2 

 

前言

  那里会扯一点C能够处理xlsm, 不过那些高档文件格式照旧上层语言处理的爽一点. C去处理真的是屠龙刀杀泥鳅.

心累.但是本文一定会处理的尽管再复杂. 就是要倚天剑烤鸡翅.

第叁来点明目小菜. C 怎么着生成一个 下面包车型地铁demo.xlsm

json 3

拍卖代码 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

json 4

 其实是大家应用的 execel处理工科具为大家解析成了excel格式. 也是一种方法吧. 欢迎尝试. 那里扯一点.

自身直接使用WPS, 也很喜欢WPS. 可是觉得WPS适合个人阅读. 对于工作处理尤其是VBA 感觉倒霉.  关于工作

中 excel => json都以里面VBA 代码在 Ctrl
+ S的时候实施的. 近日想优化一下商厦的 vba代码,不过自身用的

是wps 有心无力. (wps 处理公事恐怕有点慢, 宏和vba专业功力补助的蛋疼.)

推荐逼格高, 依旧用微软的 office最新版正式(旗舰)破解版吧. 毕竟Microsoft 是通过 office 腾飞的. 值得用破解版. 

下边我们要到正题了.

 

正文须要用的帮带文件. 前边源码中拍卖的函数调用达成都在底下文件找找到定义.

schead.h

json 5json 6

#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

json 7json 8

#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

json 9json 10

#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

json 11json 12

#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

json 13json 14

#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

json 15json 16

#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

json 17json 18

#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

json 19json 20

#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

 

正文

那大家开首把将3个xlsm如何转成工作中用的json文件. 再持续重复扯一点, 最优办法是vba, 次优方法利用上层框架例如.net 通过npoi写个批量

拍卖工具. 最伤心的格局正是本文中央银行使c处总管务代码. 好吧继续.
首先 大家照样接纳 destiny.xlsm文件 . 另存为

destiny.csv

json 21

 最终的 保存后结果为

json 22

咱俩查阅里面详细文本编码协议

json 23

末尾获得那么些文件. 大家处理格局是.

先读取 那一个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,.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个数的拼接.

运转结果如下

json 24

末段生成的 destiny.json文件结构图

json 25

到此地话. 基本的处理格局方法都早就介绍实现了. 总括C处理工科作成效事有点过时了. 上层语言还是值得大家探索的.

有趣味的能够用上层语言写一个工具来帮大家批量处理excel文件生成json. 

再扯一点. json 文书比较xml 文件, 首先内部存款和储蓄器上小了很多. 处理速度快了. 但是json文件的质量浪费在

json 26

重新的键值. 浪费空间.

再再扩张一下下. 怎么处理优化呢. 一种空间换时间, 转成内部存款和储蓄器文件防止动态解析生成. 便是确认浪费. 不过一回加载解析到内存中.

再有一种 将 对象编制程序数组去掉键值不过那样可读性很不佳.

此外,希望有更好的协商出现. 或许诞生在天涯论坛…

 

后记

  错误是难免的, 希望那一个对难题的辨析和探索对你有援救. 激发大家的思考. 有趣味的能够写写更好的工具. 交换分享.

拜~~. 

 

相关文章

网站地图xml地图