c json实战引擎四 , 最后❤跳跃

引言  - 以前那些系列

  长活短说, 写的最终 scjson 纯c跨平台引擎, 希望在合适场景中替代老的csjon引擎, 速度更快, 更轻巧.

下面也是算一个系列吧. 从cjson 中得到灵感, 外加上很久以前自己写的json引擎.  具体的可以看看下面链接.

  C json实战引擎 一 , 实现解析部分

  C json实战引擎 二 , 实现构造部分

  C json实战引擎 三 , 最后实现部分辅助函数

代码也许有点乱, 但是相信你看 cjson那个源码, 那就更乱了. 有些代码不讲道理, 好吃的东西都得让你难受一下. 才有转折.

本文是为了scjson 手写引擎添加注释解析功能. 再在跨平台上再做一些修改, 最终给出完整的测试demo.

  c json实战引擎四 , 最后❤跳跃 (这就是程序语言设计中自举例子)

     本文最终的资源  test_scjson.zip

 

前言  - 那就从json文件看起

  先看我们需要处理的 goods.json 文件

[
    /*
     * 物品定义处:
     * 物品名,品质,作用值,加血,加魔,加攻击,加防御,加速度,加幸运,价格,\
     * 占用包裹,加暴击,拥有量,最大拥有,可买(1可买,0不可买),可卖(1可卖, 0不可买)
     */
    ["小灵芝", "低级★", 1, 50, 0, 0, 0, 0, 0, 20, 1, 0, 3, 99, 1, 1],
    ["中灵芝", "中级★★", 1, 100, 0, 0, 0, 0, 0, 40, 2, 0, 1, 99, 1, 1],
    ["大灵芝", "高级★★★", 1, 200, 0, 0, 0, 0, 0, 80, 3, 0, 1, 99, 1, 1],
    ["卤肉  ", "初级★", 1, 80, 0, 0, 0, 0, 0, 30, 1, 0, 0, 99, 1, 0],
    ["小鸭脖", "初级★", 1, 100, 0, 0, 0, 0, 0, 35, 1, 0, 5, 99, 1, 1],
    ["小蓝瓶", "初级★", 1, 0, 50, 0, 0, 0, 0, 20, 1, 0, 0, 99, 1, 1],
    ["中蓝瓶", "中级★★", 1, 0, 100, 0, 0, 0, 0, 40, 2, 0, 0, 99, 1, 1],
    ["大蓝瓶", "高级★★★", 1, 0, 200, 0, 0, 0, 0, 80, 3, 0, 1, 99, 1, 1],
    ["金鳌  ", "高级★★★", 3, 0, 0, 5, 0, 0, 0, 200, 2, 0, 2, 99, 1, 1],
    ["野山椒", "中级★★", 3, 0, 0, 2, 0, 0, 0, 80, 2, 0, 1, 99, 1, 1],
    ["巨蜥肉", "高级★★★", 3, 0, 0, 0, 10, 0, 0, 100, 3, 0, 1, 99, 1, 1],
    ["龙血  ", "神级★★★★★", 3, 300, 100, 2, 2, 2, 0, 600, 5, 0, 1, 99, 0, 0],
    ["龙肉  ", "神级★★★★★", 3, 0, 0, 10, 20, 10, 0, 800, 5, 0, 1, 99, 0, 0],
    ["木剑  ", "初级★", 2, 0, 0, 10, 0, 0, 0, 50, 1, 0, 1, 1, 0, 0],
    ["木衣  ", "初级★", 2, 0, 0, 0, 10, 0, 0, 50, 1, 0, 1, 1, 0, 0],
    ["木鞋  ", "初级★", 2, 0, 0, 0, 0, 10, 0, 50, 1, 0, 1, 1, 0, 0],
    ["屠龙剑", "神级★★★★★", 2, 0, 0, 100, 0, 0, 0, 10000, 0, 0, 0, 1, 1, 1],
    ["龙皮铠甲", "神级★★★★★", 2, 0, 0, 0, 200, 0, 0, 10000, 0, 0, 0, 1, 1, 1],
    ["涅槃丹", "神级★★★★", 3, 100, 100, 100, 100, 20, 10, 5000, 1, 1, 0, 1, 1, 1]
]

扯一点我是用notepad++ 编辑的, 请安装 JsTool 插件处理json很好用

切入正题我们处理思路是, 在文件读取的时候, 去掉无效字符和注释字符. 主要code思路如下

// 从json文件中解析出最简json数据
static tstr_t _cjson_newfile(const char * path) {
    char c, n;
    tstr_t tstr;
    FILE * txt = fopen(path, "r");
    if (NULL == txt) {
        CERR("fopen r %s is error!", path);
        return NULL;
    }

    //这里创建文本串对象
    tstr = tstr_new(NULL);

    while ((c = fgetc(txt)) != EOF) {
        // step 1 : 处理字符串
        if (c == '"') {
            tstr_append(tstr, c);
            for (n = c; ((c = fgetc(txt)) != EOF) && (c != '"' || n == '\\'); n = c)
                tstr_append(tstr, c);
            if (EOF != c)
                tstr_append(tstr, c);
            continue;
        }

        // step 2 : 处理不可见特殊字符
        if (c < '!')
            continue;

        if (c == '/') {
            // step 3 : 处理 // 解析到行末尾
            n = fgetc(txt);
            if (n == '/') {
                while ((c = fgetc(txt)) != EOF && c != '\n')
                    ;
                continue;
            }

            // step 4 : 处理 /*
            if (n == '*') {
                while ((c = fgetc(txt)) != EOF) {
                    if (c == '*') {
                        n = fgetc(txt);
                        if (n == '/')
                            break;
                        ungetc(n, txt);
                    }
                }
                continue;
            }
            ungetc(n, txt);
        }

        // step 5 : 合法数据直接保存
        tstr_append(tstr, c);
    }

    fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug
    return tstr;
}

(请原谅, 这种一言不合就上源码的套路.) 主要分5类

  1. "" 字符串, 不处理直接放入

  2. 1-32 空字符直接舍弃, ['!' == 33, 可以查看ascii表]

     3. // 注释直接 舍弃到 \n, 行尾

  4. /* 舍弃到 */ 为止

  5. 合法字符直接放入

附注ascii码表如下

挺不错的, 可以自行查查.

 

同样对于json内存串也是采用相同的处理思路

/*
 * 将 jstr中 不需要解析的字符串都去掉,并且纪念mini 比男的还平
 * jstr        : 待处理的json串
 *            : 返回压缩后的json串长度
 */
static int _cjson_mini(char * jstr) {
    char c, *in = jstr, *to = jstr;

    while ((c = *to)) {
        // step 1 : 处理字符串
        if (c == '"') {
            *in++ = c;
            while ((c = *++to) && (c != '"' || to[-1] == '\\'))
                *in++ = c;
            if (c) {
                *in++ = c;
                ++to;
            }
            continue;
        }

        // step 2 : 处理不可见特殊字符
        if (c < '!') {
            ++to;
            continue;
        }

        if (c == '/') {
            // step 3 : 处理 // 解析到行末尾
            if (to[1] == '/') {
                while ((c = *++to) && c != '\n')
                    ;
                continue;
            }

            // step 4 : 处理 /*
            if (to[1] == '*') {
                while ((c = *++to) && (c != '*' || to[1] != '/'))
                    ;
                if (c)
                    to += 2;
                continue;
            }
        }

        // step 5 : 合法数据直接保存
        *in++ = *to++;
    }

    *in = '\0';
    return in - jstr;
}

到这里我们就为这个json引擎, 添加上了json注释功能了, 下面会搭建测试环境.

 

正文  - 测试环境搭建

  跨平台的 scjson引擎[simple c json] , 跨平台的, 采用 Ubuntu linux 搭建一下. window上更好搞. 首先得到所有文件附在下面

 schead.h 

#ifndef _H_SIMPLEC_SCHEAD
#define _H_SIMPLEC_SCHEAD

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include <stdbool.h>
#include <scalloc.h>

/*
 * error        => 以后再说
 * 跨平台的丑陋从这里开始
 * __GNUC        => linux 平台特殊操作
 * __MSC_VER    => window 平台特殊操作
 */
#ifdef __GNUC__  // 下面是依赖GCC编译器实现

#include <unistd.h>
#include <sys/time.h>
#include <termio.h>

// 统一的程序睡眠宏, 单位是毫秒颗粒度
#define SLEEPMS(m) \
        usleep(m * 1000)

// 屏幕清除宏, 依赖系统脚本
#define CONSOLECLS() \
        system("printf '\ec'")

/*
 * 得到用户输入的一个字符
 *        : 返回得到字符
 */
extern int sh_getch(void);

#elif _MSC_VER // 下面是依赖Visual Studio编译器实现

#include <Windows.h>
#include <direct.h> 
#include <conio.h>

// window 删除目录宏
#define rmdir        _rmdir

// window 上用_getch 替代了getch, 这里为了让其回来
#define sh_getch    _getch

#define CONSOLECLS() \
        system("cls")

#define SLEEPMS(m) \
        Sleep(m)    

#else
    #error "error : Currently only supports the Visual Studio and GCC!"
#endif


 /*
  * 错误定义枚举 用于判断返回值状态的状态码 RT_*表示返回标志
  *    使用举例 :
  
    int flag = scconf_get("pursue");
    if(flag < RT_SuccessBase) {
        sclog_error("get config %s error! flag = %d.", "pursue", flag);
        exit(EXIT_FAILURE);
    }
  
  * 这里是内部 使用的通用返回值 标志. >=0 表示成功, <0 表示失败的情况
  */
typedef enum {
    RT_SuccessBase    = 00,                //结果正确的返回宏
    RT_ErrorBase    = -1,                //错误基类型, 所有错误都可用它, 在不清楚的情况下
    RT_ErrorParam    = -2,                //调用的参数错误
    RT_ErrorMalloc    = -3,                //内存分配错误
    RT_ErrorFopen    = -4,                //文件打开失败    
    RT_ErrorClose    = -5,                //文件描述符读取关闭, 读取完毕也会返回这个
} flag_e;

/*
 * 定义一些通用的函数指针帮助,主要用于基库的封装中
 * 有构造函数, 释放函数, 比较函数等
 */
typedef void *    (* pnew_f)();
typedef void    (* vdel_f)(void * node);
// icmp_f 最好 是 int cmp(const void * ln, const void * rn); 标准结构
typedef int        (* icmp_f)();
// 循环操作函数, arg 外部参数, node 内部节点
typedef flag_e    (* each_f)(void * node, void * arg);

/*
 * c 如果是空白字符返回 true, 否则返回false
 * c : 必须是 int 值,最好是 char 范围
 */
#define sh_isspace(c) \
    ((c==' ')||(c>='\t'&&c<='\r'))

// 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 "\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 // !CERR_EXIT

// 4.2 执行后检测,如果有错误直接退出
#ifndef IF_CHECK
#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 */

// 7.0 置空操作
#ifndef BZERO
// v必须是个变量
#define BZERO(v) \
    memset(&(v), 0, sizeof(v))
#endif/* !BZERO */    

// 9.0 scanf 健壮的
#ifndef SAFETY_SCANF
#define _STR_SAFETY_SCANF "Input error, please according to the prompt!"
#define SAFETY_SCANF(scanf_code, ...) \
    while(printf(__VA_ARGS__), scanf_code){\
        while('\n' != getchar()) \
            ;\
        puts(_STR_SAFETY_SCANF);\
    }\
    while('\n' != getchar())
#endif /*!SAFETY_SCANF*/

// 简单的time帮助宏
#ifndef TIME_PRINT
#define _STR_TIME_PRINT "The current code block running time:%lf seconds\n"
#define TIME_PRINT(code) \
    do{\
        clock_t __st, __et;\
        __st=clock();\
        code\
        __et=clock();\
        printf(_STR_TIME_PRINT, (0.0 + __et - __st) / CLOCKS_PER_SEC);\
    } while(0)
#endif // !TIME_PRINT

/*
 * 10.0 这里是一个 在 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 "Press any key to continue . . ."
extern void sh_pause(void);
#ifndef INIT_PAUSE

#    ifdef _DEBUG
#        define INIT_PAUSE() atexit(sh_pause)
#    else
#        define INIT_PAUSE()    /* 别说了,都重新开始吧 */
#    endif

#endif // !INIT_PAUSE

//12.0 判断是大端序还是小端序,大端序返回true
extern bool sh_isbig(void);

/**
*    sh_free - 简单的释放内存函数,对free再封装了一下
**可以避免野指针
**pobj:指向待释放内存的指针(void*)
**/
extern void sh_free(void ** pobj);

/*
 * 比较两个结构体栈上内容是否相等,相等返回true,不等返回false
 * a    : 第一个结构体值
 * b    : 第二个结构体值
 *        : 相等返回true, 否则false
 */
#define STRUCTCMP(a, b) \
    (!memcmp(&a, &b, sizeof(a)))

#endif// ! _H_SIMPLEC_SCHEAD
View Code

 schead.c

#include <schead.h>

//简单通用的等待函数
inline void
sh_pause(void) {
    rewind(stdin);
    printf(_STR_PAUSEMSG);
    sh_getch();
}

//12.0 判断是大端序还是小端序,大端序返回true
inline 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;
}

// 为linux扩展一些功能
#if defined(__GNUC__)

/*
 * 得到用户输入的一个字符
 *        : 返回得到字符
 */
int 
sh_getch(void) {
    int cr;
    struct termios nts, ots;

    if (tcgetattr(0, &ots) < 0) // 得到当前终端(0表示标准输入)的设置
        return EOF;

    nts = ots;
    cfmakeraw(&nts); // 设置终端为Raw原始模式,该模式下所有的输入数据以字节为单位被处理
    if (tcsetattr(0, TCSANOW, &nts) < 0) // 设置上更改之后的设置
        return EOF;

    cr = getchar();
    if (tcsetattr(0, TCSANOW, &ots) < 0) // 设置还原成老的模式
        return EOF;

    return cr;
}

#endif
View Code

 

scalloc.h

#ifndef _H_SIMPLEC_SCALLOC
#define _H_SIMPLEC_SCALLOC

#include <stdlib.h>

// 释放sm_malloc_和sm_realloc_申请的内存, 必须配套使用
void sm_free_(void * ptr, const char * file, int line, const char * func);
// 返回申请的一段干净的内存
void * sm_malloc_(size_t sz, const char * file, int line, const char * func);
// 返回重新申请的内存, 只能和sm_malloc_配套使用
void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func);

/*
 * 释放申请的内存
 * ptr    : 申请的内存
 */
#define sm_free(ptr)        sm_free_(ptr, __FILE__, __LINE__, __func__)
/*
 * 返回申请的内存, 并且是填充'\0'
 * sz    : 申请内存的长度
 */
#define sm_malloc(sz)        sm_malloc_(sz, __FILE__, __LINE__, __func__)
/*
 * 返回申请到num*sz长度内存, 并且是填充'\0'
 * num    : 申请的数量
 * sz    : 申请内存的长度
 */
#define sm_calloc(num, sz)    sm_malloc_(num*sz, __FILE__, __LINE__, __func__)
/*
 * 返回重新申请的内存
 * ptr    : 申请的内存
 * sz    : 申请内存的长度
 */
#define sm_realloc(ptr, sz)    sm_realloc_(ptr, sz, __FILE__, __LINE__, __func__)

// 定义全局内存使用宏, 替换原有的malloc系列函数
#ifndef _SIMPLEC_ALLOC_CLOSE
#    define free            sm_free
#    define malloc        sm_malloc
#    define calloc        sm_calloc
#    define realloc        sm_realloc
#endif

#endif // !_H_SIMPLEC_SCALLOC
View Code

scalloc.c

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

// 标识枚举
typedef enum {
    HF_Alloc,
    HF_Free
} header_e;

// 每次申请内存的[16-24]字节额外消耗, 用于记录内存申请情况
struct header {
    header_e flag;        // 当前内存使用的标识
    int line;            // 申请的文件行
    const char * file;    // 申请的文件名
    const char * func;    // 申请的函数名
};

// 内部使用的malloc, 返回内存会用'\0'初始化
void * 
sm_malloc_(size_t sz, const char * file, int line, const char * func) {
    struct header * ptr = malloc(sz + sizeof(struct header));
    // 检查内存分配的结果
    if(NULL == ptr) {
        fprintf(stderr, "_header_get >%s:%d:%s< alloc error not enough memory start fail!\n", file, line, func);
        exit(EXIT_FAILURE);
    }

    ptr->flag = HF_Alloc;
    ptr->line = line;
    ptr->file = file;
    ptr->func = func;
    memset(++ptr, 0, sz);
    return ptr;
}

// 得到申请内存的开头部分, 并检查
static struct header * _header_get(void * ptr, const char * file, int line, const char * func) {
    struct header * node = (struct header *)ptr - 1;
    // 正常情况直接返回
    if(HF_Alloc != node->flag) {    
        // 异常情况, 内存多次释放, 和内存无效释放
        fprintf(stderr, "_header_get free invalid memony flag %d by >%s:%d:%s<\n", node->flag, file, line, func);
        exit(EXIT_FAILURE);
    }
    return node;
}

// 内部使用的realloc
void * 
sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func) {
    struct header * node , * buf;
    if(NULL == ptr)
        return sm_malloc_(sz, file, line, func);
    
    // 合理内存分割
    node = _header_get(ptr, file, line, func);
    node->flag = HF_Free;
    // 构造返回内存信息
    buf = realloc(node, sz + sizeof(struct header));
    buf->flag = HF_Alloc;
    buf->line = line;
    buf->file = file;
    buf->func = func;

    return buf + 1;
}

// 内部使用的free, 每次释放都会打印日志信息
void 
sm_free_(void * ptr, const char * file, int line, const char * func) {
    if(NULL !=  ptr) {
        // 得到内存地址, 并且标识一下, 开始释放
        struct header * node = _header_get(ptr, file, line, func);
        node->flag = HF_Free;
        free(node);
    }
}
View Code

 

tstr.h

#ifndef _H_SIMPLEC_TSTR
#define _H_SIMPLEC_TSTR

#include <schead.h>

//------------------------------------------------简单字符串辅助操作----------------------------------

/*
 * 主要采用jshash 返回计算后的hash值
 * 不冲突率在 80% 左右还可以, 不要传入NULL
 */
extern unsigned tstr_hash(const char * str);

/*
 * 这是个不区分大小写的比较函数
 * ls        : 左边比较字符串
 * rs        : 右边比较字符串
 *            : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0
 */
extern int tstr_icmp(const char * ls, const char * rs);

/*
 * 这个代码是 对 strdup 的再实现, 调用之后需要free 
 * str        : 待复制的源码内容
 *            : 返回 复制后的串内容
 */
extern char * tstr_dup(const char * str);

//------------------------------------------------简单文本字符串辅助操作----------------------------------

#ifndef _STRUCT_TSTR
#define _STRUCT_TSTR
//简单字符串结构,并定义文本字符串类型tstring
struct tstr {
    char * str;        //字符串实际保存的内容
    int len;        //当前字符串大小
    int size;        //字符池大小
};
typedef struct tstr * tstr_t;
#endif // !_STRUCT_TSTR

//文本串栈上创建内容,不想用那些技巧了,就这样吧
#define TSTR_NEW(var) \
    struct tstr $__##var = { NULL, 0, 0 }, * var = &$__##var;
#define TSTR_DELETE(var) \
    sm_free(var->str)

/*
 * tstr_t 的创建函数, 会根据str创建一个 tstr_t 结构的字符串
 * str    : 待创建的字符串
 *        : 返回创建好的字符串,如果创建失败返回NULL
 */
extern tstr_t tstr_new(const char * str);

/*
 * tstr_t 析构函数
 * tstr : tstr_t字符串指针量
 */
extern void tstr_delete(tstr_t tstr);

/*
 *  向简单文本字符串tstr中添加 一个字符c
 * tstr : 简单字符串对象
 * c    : 待添加的字符
 */
extern void tstr_append(tstr_t tstr, int c);

/*
 *  向简单文本串中添加只读字符串 
 * tstr    : 文本串
 * str    : 待添加的素材串
 */
extern void tstr_appends(tstr_t tstr, const char * str);

/*
 * 复制tstr中内容,得到char *, 需要自己 free释放
 * 假如你要清空tstr_t字符串只需要 设置 len = 0.就可以了
 * tstr    : 待分配的字符串
 *        : 返回分配好的字符串首地址
 */
extern char * tstr_dupstr(tstr_t tstr);

//------------------------------------------------简单文件辅助操作----------------------------------

/*
 * 简单的文件帮助类,会读取完毕这个文件内容返回,失败返回NULL.
 * 需要事后使用 tstr_delete(ret); 销毁这个字符串对象
 * path    : 文件路径
 *        : 返回创建好的字符串内容,返回NULL表示读取失败
 */
extern tstr_t tstr_file_readend(const char * path);

/*
 * 文件写入,没有好说的, 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen
 * path    : 文件路径
 * str    : 待写入的字符串
 *        : 返回操作的结果 见上面枚举
 */
extern int tstr_file_writes(const char * path, const char * str);

/*
 * 文件追加内容 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen
 * path    : 文件路径
 * str    : 待写入的字符串
 *        : 返回操作的结果 见上面枚举
 */
extern int tstr_file_append(const char * path, const char * str);

#endif // !_H_SIMPLEC_TSTR
View Code

tstr.c

#include <tstr.h>

/*
 * 主要采用jshash 返回计算后的hash值
 * 不冲突率在 80% 左右还可以, 不要传入NULL
 */
unsigned 
tstr_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 
tstr_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 * 
tstr_dup(const char * str)
{
    size_t len; 
    char * nstr;
    if (NULL == str)
        return NULL;

    len = sizeof(char) * (strlen(str) + 1);
    nstr = sm_malloc(len);
    // 返回最后结果
    return memcpy(nstr, str, len);
}

//------------------------------------------------简单文本字符串辅助操作----------------------------------

/*
 * tstr_t 的创建函数, 会根据str创建一个 tstr_t 结构的字符串
 * str    : 待创建的字符串
 *        : 返回创建好的字符串,如果创建失败返回NULL
 */
tstr_t 
tstr_new(const char * str) {
    tstr_t tstr = sm_malloc(sizeof(struct tstr));
    tstr_appends(tstr, str);
    return tstr;
}

/*
 * tstr_t 析构函数
 * tstr : tstr_t字符串指针量
 */
inline void 
tstr_delete(tstr_t tstr) {
    if (tstr) {
        sm_free(tstr->str);
        sm_free(tstr);
    }
}

//文本字符串创建的度量值
#define _INT_TSTRING (32)

//简单分配函数,智力一定会分配内存的, len > size的时候调用这个函数
static void _tstr_realloc(tstr_t tstr, int len)
{
    int size = tstr->size;
    for (size = size < _INT_TSTRING ? _INT_TSTRING : size; size < len; size <<= 1)
        ;
    //分配内存
    tstr->str = sm_realloc(tstr->str, size);
    tstr->size = size;
}

/*
 *  向简单文本字符串tstr中添加 一个字符c
 * tstr : 简单字符串对象
 * c    : 待添加的字符
 */
void 
tstr_append(tstr_t tstr, int c) {
    //不做安全检查
    int len = tstr->len + 1 + 1; // c + '\0' 而len只指向 字符串strlen长度

    //需要的❀, 需要进行内存分配, 唯一损失
    if (len > tstr->size)
        _tstr_realloc(tstr, len);

    tstr->len = --len;
    tstr->str[len - 1] = c;
    tstr->str[len] = '\0';
}

/*
 *  向简单文本串中添加只读字符串
 * tstr    : 文本串
 * str    : 待添加的素材串
 *        : 返回状态码主要是 _RT_EP _RT_EM
 */
void 
tstr_appends(tstr_t tstr, const char * str) {
    int len;
    if (!tstr || !str || !*str)
        return;

    // 检查内存是否需要重新构建
    len = tstr->len + (int)strlen(str) + 1;
    if (len > tstr->size)
        _tstr_realloc(tstr, len);

    strcpy(tstr->str + tstr->len, str);
    tstr->len = len - 1;
}

/*
 * 复制tstr中内容,得到char *, 需要自己 free释放
 * 假如你要清空tstr_t字符串只需要 设置 len = 0.就可以了
 * tstr    : 待分配的字符串
 *        : 返回分配好的字符串首地址
 */
char * 
tstr_dupstr(tstr_t tstr) {
    char * str;
    if (!tstr || tstr->len <= 0)
        return NULL;
    
    //下面就可以复制了,采用最快的一种方式
    str = sm_malloc(tstr->len + 1);
    return memcpy(str, tstr->str, tstr->len + 1);
}

//------------------------------------------------简单文件辅助操作----------------------------------

/*
 * 简单的文件帮助类,会读取完毕这个文件内容返回,失败返回NULL.
 * 需要事后使用 tstr_delete(ret); 销毁这个字符串对象
 * path    : 文件路径
 *        : 返回创建好的字符串内容,返回NULL表示读取失败
 */
tstr_t 
tstr_file_readend(const char * path) {
    int c;
    tstr_t tstr;
    FILE * txt = fopen(path, "r");
    if (NULL == txt) {
        CERR("fopen r %s is error!", path);
        return NULL;
    }

    //这里创建文本串对象
    tstr = tstr_new(NULL);

    //这里读取文本内容
    while ((c = fgetc(txt)) != EOF)
        tstr_append(tstr, c);

    fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug
    return tstr;
}

int _tstr_file_writes(const char * path, const char * str, const char * mode) {
    FILE* txt;
    // 检查参数是否有问题
    if (!path || !*path || !str) {
        CERR("check is '!path || !*path || !str'");
        return RT_ErrorParam;
    }

    if ((txt = fopen(path, mode)) == NULL) {
        CERR("fopen mode = '%s', path = '%s' error!", mode, path);
        return RT_ErrorFopen;
    }

    //这里写入信息
    fputs(str, txt);

    fclose(txt);
    return RT_SuccessBase;
}

/*
 * 文件写入,没有好说的, 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen
 * path    : 文件路径
 * str    : 待写入的字符串
 *        : 返回操作的结果 见上面枚举
 */
inline int
tstr_file_writes(const char * path, const char * str) {
    return _tstr_file_writes(path, str, "wb");
}

/*
 * 文件追加内容 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen
 * path    : 文件路径
 * str    : 待写入的字符串
 *        : 返回操作的结果 见上面枚举
 */
inline int 
tstr_file_appends(const char * path, const char * str) {
    return _tstr_file_writes(path, str, "ab");
}
View Code

 

scjson.h

#ifndef _H_SIMPLEC_SCJSON
#define _H_SIMPLEC_SCJSON

#include <tstr.h>

// json 中几种数据类型定义 , 对于C而言 最难的是看不见源码,而不是api复杂, 更不是业务复杂
#define _CJSON_FALSE    (0)
#define _CJSON_TRUE        (1)
#define _CJSON_NULL        (2)
#define _CJSON_NUMBER    (3)
#define _CJSON_STRING    (4)
#define _CJSON_ARRAY    (5)
#define _CJSON_OBJECT    (6)

#define _CJSON_ISREF    (256)        //set 时候用如果是引用就不释放了
#define _CJSON_ISCONST    (512)        //set时候用, 如果是const char* 就不释放了

struct cjson {
    struct cjson * next, * prev;
    struct cjson * child; // type == _CJSON_ARRAY or type == _CJSON_OBJECT 那么 child 就不为空

    int type;
    char * key;    // json内容那块的 key名称     
    char * vs;    // type == _CJSON_STRING, 是一个字符串     
    double vd;  // type == _CJSON_NUMBER, 是一个num值, ((int)c->vd) 转成int 或 bool
};

//定义cjson_t json类型
typedef struct cjson * cjson_t;

/*
 * 这个宏,协助我们得到 int 值 或 bool 值 
 * 
 * item : 待处理的目标cjson_t结点
 */
#define cjson_getint(item) \
    ((int)((item)->vd))

/*
 *  删除json串内容  
 *  c        : 待释放json_t串内容
 */
extern void cjson_delete(cjson_t c);

/*
 * 对json字符串解析返回解析后的结果
 * jstr        : 待解析的字符串
 */
extern cjson_t cjson_newtstr(tstr_t str);

/*
 *    将json文件解析成json内容返回. 需要自己调用 cjson_delete
 * path        : json串路径
 *            : 返回处理好的cjson_t 内容,失败返回NULL
 */
extern cjson_t cjson_newfile(const char * path);

/*
 * 根据 item当前结点的 next 一直寻找到 NULL, 返回个数. 推荐在数组的时候使用
 * array    : 待处理的cjson_t数组对象
 *            : 返回这个数组中长度
 */
extern int cjson_getlen(cjson_t array);

/*
 * 根据索引得到这个数组中对象
 * array    : 数组对象
 * idx        : 查找的索引 必须 [0,cjson_getlen(array)) 范围内
 *            : 返回查找到的当前对象
 */
extern cjson_t cjson_getarray(cjson_t array, int idx);

/*
 * 根据key得到这个对象 相应位置的值
 * object    : 待处理对象中值
 * key        : 寻找的key
 *            : 返回 查找 cjson_t 对象
 */
extern cjson_t cjson_getobject(cjson_t object, const char * key);


// --------------------------------- 下面是 cjson 输出部分的处理代码 -----------------------------------------

/*
 *  这里是将 cjson_t item 转换成字符串内容,需要自己free 
 * item        : cjson的具体结点
 *            : 返回生成的item的json串内容
 */
extern char* cjson_print(cjson_t item);

// --------------------------------- 下面是 cjson 输出部分的辅助代码 -----------------------------------------

/*
 * 创建一个bool的对象 b==0表示false,否则都是true, 需要自己释放 cjson_delete
 * b        : bool 值 最好是 _Bool
 *            : 返回 创建好的json 内容
 */
extern cjson_t cjson_newnull();
extern cjson_t cjson_newbool(int b);
extern cjson_t cjson_newnumber(double vd);
extern cjson_t cjson_newstring(const char * vs);
extern cjson_t cjson_newarray(void);
extern cjson_t cjson_newobject(void);

/*
 * 按照类型,创建 对映类型的数组 cjson对象
 *目前支持 _CJSON_NULL _CJSON_BOOL/FALSE or TRUE , _CJSON_NUMBER, _CJSON_STRING
 * NULL => array 传入NULL, FALSE 使用char[],也可以传入NULL, NUMBER 只接受double, string 只接受char**
 * type        : 类型目前支持 上面几种类型
 * array    : 数组原始数据
 * len        : 数组中元素长度
 *            : 返回创建的数组对象
 */
extern cjson_t cjson_newtypearray(int type, const void * array, int len);

/*
 * 在array中分离第idx个索引项内容.
 * array    : 待处理的json_t 数组内容
 * idx        : 索引内容
 *            : 返回分离的json_t内容
 */
extern cjson_t cjson_detacharray(cjson_t array, int idx);

/*
 * 在object json 中分离 key 的项出去
 * object    : 待分离的对象主体内容
 * key        : 关联的键
 *            : 返回分离的 object中 key的项json_t
 */
extern cjson_t cjson_detachobject(cjson_t object, const char * key);

#endif // !_H_SIMPLEC_SCJSON
View Code

scjson.c

#include <scjson.h>
#include <float.h>
#include <limits.h>
#include <math.h>

// 删除cjson
static void _cjson_delete(cjson_t c) {
    cjson_t next;
    while (c) {
        next = c->next;
        //递归删除儿子
        if (!(c->type & _CJSON_ISREF)) {
            if (c->child) //如果不是尾递归,那就先递归
                _cjson_delete(c->child);
            if (c->vs)
                sm_free(c->vs);
        }
        else if (!(c->type & _CJSON_ISCONST) && c->key)
            sm_free(c->key);
        sm_free(c);
        c = next;
    }
}

/*
*  删除json串内容,最近老是受清华的老学生打击, 会起来的......
*  c        : 待释放json_t串内容
*/
inline void 
cjson_delete(cjson_t c) {
    if (NULL == c)
        return;
    _cjson_delete(c);
}

//构造一个空 cjson 对象
static inline cjson_t _cjson_new(void) {
    return sm_malloc(sizeof(struct cjson));
}

// 简化的代码段,用宏来简化代码书写 , 16进制处理
#define __parse_hex4_code(c, h) \
    if (c >= '0' && c <= '9') \
        h += c - '0'; \
    else if (c >= 'A' && c <= 'F') \
        h += 10 + c - 'A'; \
    else if (c >= 'a' && c <= 'z') \
        h += 10 + c - 'F'; \
    else \
        return 0

// 等到unicode char代码
static unsigned _parse_hex4(const char * str) {
    unsigned h = 0;
    char c = *str;
    //第一轮
    __parse_hex4_code(c, h);
    h <<= 4;
    c = *++str;
    //第二轮
    __parse_hex4_code(c, h);
    h <<= 4;
    c = *++str;
    //第三轮
    __parse_hex4_code(c, h);
    h <<= 4;
    c = *++str;
    //第四轮
    __parse_hex4_code(c, h);

    return h;
}

// 分析字符串的子函数,
static const char* _parse_string(cjson_t item, const char * str) {
    static unsigned char _marks[] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
    const char * ptr;
    char * nptr, * out;
    char c;
    int len;
    unsigned uc, nuc;

    if (*str != '\"') { // 检查是否是字符串内容
        CERR("need \\\" str => %s error!", str);
        return NULL;
    }

    for (ptr = str + 1, len = 0; (c = *ptr++) != '\"' && c; ++len)
        if (c == '\\') //跳过转义字符
            ++ptr;
    out = sm_malloc(len + 1);
    // 这里复制拷贝内容
    for (ptr = str + 1, nptr = out; (c = *ptr) != '\"' && c; ++ptr) {
        if (c != '\\') {
            *nptr++ = c;
            continue;
        }
        // 处理转义字符
        switch ((c = *++ptr)) {
        case 'b': *nptr++ = '\b'; break;
        case 'f': *nptr++ = '\f'; break;
        case 'n': *nptr++ = '\n'; break;
        case 'r': *nptr++ = '\r'; break;
        case 't': *nptr++ = '\t'; break;
        case 'u': // 将utf16 => utf8, 专门的utf处理代码
            uc = _parse_hex4(ptr + 1);
            ptr += 4;//跳过后面四个字符, unicode
            if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0)    break;    /* check for invalid.    */

            if (uc >= 0xD800 && uc <= 0xDBFF) { /* UTF16 surrogate pairs.    */
                if (ptr[1] != '\\' || ptr[2] != 'u')    
                    break;    /* missing second-half of surrogate.    */
                nuc = _parse_hex4(ptr + 3);
                ptr += 6;
                if (nuc < 0xDC00 || nuc>0xDFFF)        
                    break;    /* invalid second-half of surrogate.    */
                uc = 0x10000 + (((uc & 0x3FF) << 10) | (nuc & 0x3FF));
            }

            len = 4;
            if (uc < 0x80) 
                len = 1;
            else if (uc < 0x800) 
                len = 2;
            else if (uc < 0x10000) 
                len = 3; 
            nptr += len;

            switch (len) {
            case 4: *--nptr = ((uc | 0x80) & 0xBF); uc >>= 6;
            case 3: *--nptr = ((uc | 0x80) & 0xBF); uc >>= 6;
            case 2: *--nptr = ((uc | 0x80) & 0xBF); uc >>= 6;
            case 1: *--nptr = (uc | _marks[len]);
            }
            nptr += len;
            break;
        default: *nptr++ = c;
        }
    }

    *nptr = '\0';
    if (c == '\"')
        ++ptr;
    item->vs = out;
    item->type = _CJSON_STRING;
    return ptr;
}

// 分析数值的子函数,写的可以
static const char * _parse_number(cjson_t item, const char * str) {
    double n = 0.0, ns = 1.0, nd = 0.0; //n把偶才能值, ns表示开始正负, 负为-1, nd 表示小数后面位数
    int e = 0, es = 1; //e表示后面指数, es表示 指数的正负,负为-1
    char c;

    if ((c = *str) == '-' || c == '+') {
        ns = c == '-' ? -1.0 : 1.0; //正负号检测, 1表示负数
        ++str;
    }
    //处理整数部分
    for (c = *str; c >= '0' && c <= '9'; c = *++str)
        n = n * 10 + c - '0';
    if (c == '.')
        for (; (c = *++str) >= '0' && c <= '9'; --nd)
            n = n * 10 + c - '0';

    // 处理科学计数法
    if (c == 'e' || c == 'E') {
        if ((c = *++str) == '+') //处理指数部分
            ++str;
        else if (c == '-')
            es = -1, ++str;
        for (; (c = *str) >= '0' && c <= '9'; ++str)
            e = e * 10 + c - '0';
    }

    //返回最终结果 number = +/- number.fraction * 10^+/- exponent
    n = ns * n * pow(10.0, nd + es * e);
    item->vd = n;
    item->type = _CJSON_NUMBER;
    return str;
}

// 递归下降分析 需要声明这些函数
static const char * _parse_array(cjson_t item, const char * str);
static const char * _parse_object(cjson_t item, const char * str);
static const char * _parse_value(cjson_t item, const char * value);

// 分析数组的子函数, 采用递归下降分析
static const char * _parse_array(cjson_t item, const char * str) {
    cjson_t child;
    if (*str != '[') {
        CERR("array str error start: %s.", str);
        return NULL;
    }

    item->type = _CJSON_ARRAY;
    str = str + 1;
    if (*str == ']') // 低估提前结束
        return str + 1;

    item->child = child = _cjson_new();
    str = _parse_value(child, str);
    if (NULL == str) { // 解析失败 直接返回
        CERR("array str error e n d one: %s.", str);
        return NULL;
    }
    while (*str == ',') {
        cjson_t nitem = _cjson_new();
        child->next = nitem;
        nitem->prev = child;
        child = nitem;
        str = _parse_value(child, str + 1);
        if (NULL == str) {// 写代码是一件很爽的事
            CERR("array str error e n d two: %s.", str);
            return NULL;
        }
    }

    if (*str != ']') {
        CERR("array str error e n d: %s.", str);
        return NULL;
    }
    return str + 1; // 跳过']'
}

// 分析对象的子函数
static const char * _parse_object(cjson_t item, const char * str) {
    cjson_t child;
    if (*str != '{') {
        CERR("object str error start: %s.", str);
        return NULL;
    }

    item->type = _CJSON_OBJECT;
    str = str + 1;
    if (*str == '}')
        return str + 1;

    //处理结点, 开始读取一个 key
    item->child = child = _cjson_new();
    str = _parse_string(child, str);
    if (!str || *str != ':') {
        CERR("_skip _parse_string is error : %s!", str);
        return NULL;
    }
    child->key = child->vs;
    child->vs = NULL;

    str = _parse_value(child, str + 1);
    if (!str) {
        CERR("_parse_object _parse_value is error 2!");
        return NULL;
    }

    // 递归解析
    while (*str == ',') {
        cjson_t nitem = _cjson_new();
        child->next = nitem;
        nitem->prev = child;
        child = nitem;
        str = _parse_string(child, str + 1);
        if (!str || *str != ':'){
            CERR("_parse_string need name or no equal ':' %s.", str);
            return NULL;
        }
        child->key = child->vs;
        child->vs = NULL;

        str = _parse_value(child, str+1);
        if (!str) {
            CERR("_parse_string need item two ':' %s.", str);
            return NULL;
        }
    }

    if (*str != '}') {
        CERR("object str error e n d: %s.", str);
        return NULL;
    }
    return str + 1;
}

// 将value 转换塞入 item json值中一部分
static const char * _parse_value(cjson_t item, const char * value) {
    char c; 
    if ((value) && (c = *value)) {
        switch (c) {
            // n = null, f = false, t = true
        case 'n' : return item->type = _CJSON_NULL, value + 4;
        case 'f' : return item->type = _CJSON_FALSE, value + 5;
        case 't' : return item->type = _CJSON_TRUE, item->vd = 1.0, value + 4;
        case '\"': return _parse_string(item, value);
        case '0' : case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
        case '+' : case '-': return _parse_number(item, value);
        case '[' : return _parse_array(item, value);
        case '{' : return _parse_object(item, value);
        }
    }
    // 循环到这里是意外 数据
    CERR("params value = %s!", value);
    return NULL;
}

/*
 * 对json字符串解析返回解析后的结果
 * jstr        : 待解析的字符串
 *            : 返回解析好的字符串内容
 */
static cjson_t _cjson_parse(const char * jstr) {
    cjson_t c = _cjson_new();
    const char * end;

    if (!(end = _parse_value(c, jstr))) {
        CERR("_parse_value params end = %s!", end);
        cjson_delete(c);
        return NULL;
    }

    //这里是否检测 返回测试数据
    return c;
}

/*
 * 将 jstr中 不需要解析的字符串都去掉,并且纪念mini 比男的还平
 * jstr        : 待处理的json串
 *            : 返回压缩后的json串长度
 */
static int _cjson_mini(char * jstr) {
    char c, *in = jstr, *to = jstr;

    while ((c = *to)) {
        // step 1 : 处理字符串
        if (c == '"') {
            *in++ = c;
            while ((c = *++to) && (c != '"' || to[-1] == '\\'))
                *in++ = c;
            if (c) {
                *in++ = c;
                ++to;
            }
            continue;
        }

        // step 2 : 处理不可见特殊字符
        if (c < '!') {
            ++to;
            continue;
        }

        if (c == '/') {
            // step 3 : 处理 // 解析到行末尾
            if (to[1] == '/') {
                while ((c = *++to) && c != '\n')
                    ;
                continue;
            }

            // step 4 : 处理 /*
            if (to[1] == '*') {
                while ((c = *++to) && (c != '*' || to[1] != '/'))
                    ;
                if (c)
                    to += 2;
                continue;
            }
        }

        // step 5 : 合法数据直接保存
        *in++ = *to++;
    }

    *in = '\0';
    return in - jstr;
}

/*
 * 对json字符串解析返回解析后的结果
 * jstr        : 待解析的字符串
 */
inline cjson_t 
cjson_newtstr(tstr_t str) {
    str->len = _cjson_mini(str->str);
    return _cjson_parse(str->str);
}

// 从json文件中解析出最简json数据
static tstr_t _cjson_newfile(const char * path) {
    char c, n;
    tstr_t tstr;
    FILE * txt = fopen(path, "r");
    if (NULL == txt) {
        CERR("fopen r %s is error!", path);
        return NULL;
    }

    //这里创建文本串对象
    tstr = tstr_new(NULL);

    while ((c = fgetc(txt)) != EOF) {
        // step 1 : 处理字符串
        if (c == '"') {
            tstr_append(tstr, c);
            for (n = c; ((c = fgetc(txt)) != EOF) && (c != '"' || n == '\\'); n = c)
                tstr_append(tstr, c);
            if (EOF != c)
                tstr_append(tstr, c);
            continue;
        }

        // step 2 : 处理不可见特殊字符
        if (c < '!')
            continue;

        if (c == '/') {
            // step 3 : 处理 // 解析到行末尾
            n = fgetc(txt);
            if (n == '/') {
                while ((c = fgetc(txt)) != EOF && c != '\n')
                    ;
                continue;
            }

            // step 4 : 处理 /*
            if (n == '*') {
                while ((c = fgetc(txt)) != EOF) {
                    if (c == '*') {
                        n = fgetc(txt);
                        if (n == '/')
                            break;
                        ungetc(n, txt);
                    }
                }
                continue;
            }
            ungetc(n, txt);
        }

        // step 5 : 合法数据直接保存
        tstr_append(tstr, c);
    }

    fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug
    return tstr;
}

/*
 *    将json文件解析成json内容返回. 需要自己调用 cjson_delete
 * path        : json串路径
 *            : 返回处理好的cjson_t 内容,失败返回NULL
 */
cjson_t
cjson_newfile(const char * path) {
    cjson_t root;
    tstr_t tstr = _cjson_newfile(path);
    if (!tstr) {
        CERR("_cjson_dofile_tstr path:%s is error!", path);
        return NULL;
    }
    root = _cjson_parse(tstr->str);
    tstr_delete(tstr);
    return root;
}

/*
 * 根据 item当前结点的 next 一直寻找到 NULL, 返回个数. 推荐在数组的时候使用
 * array    : 待处理的cjson_t数组对象
 *            : 返回这个数组中长度
 */
int 
cjson_getlen(cjson_t array) {
    int len = 0;
    if (array) {
        for (array = array->child; array; array = array->next)
            ++len;
    }
    return len;
}

/*
 * 根据索引得到这个数组中对象
 * array    : 数组对象
 * idx        : 查找的索引 必须 [0,cjson_getlen(array)) 范围内
 *            : 返回查找到的当前对象
 */
cjson_t 
cjson_getarray(cjson_t array, int idx) {
    cjson_t c;
    DEBUG_CODE({
        if (!array || idx < 0) {
            CERR("array:%p, idx=%d params is error!", array, idx);
            return NULL;
        }
    });

    for (c = array->child; c&&idx > 0; c = c->next)
        --idx;

    return c;
}

/*
 * 根据key得到这个对象 相应位置的值
 * object    : 待处理对象中值
 * key        : 寻找的key
 *            : 返回 查找 cjson_t 对象
 */
cjson_t 
cjson_getobject(cjson_t object, const char* key) {
    cjson_t c;
    DEBUG_CODE({
        if (!object || !key || !*key) {
            CERR("object:%p, key=%s params is error!", object, key);
            return NULL;
        }
    });

    for (c = object->child; c && tstr_icmp(key, c->key); c = c->next)
        ;

    return c;
}

// --------------------------------- 下面是 cjson 输出部分的处理代码 -----------------------------------------

// 2^n>=x , n是最小的整数
static int _pow2gt(int x) {
    --x;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return x + 1;
}

/*
 *     这里使用 tstr_t 结构 size 这里表示 字符串总大小,没有变化
 * len 表示当前字符串的字符串起始偏移量 即 tstr_t->str + tstr_t->len 起始的
 */
static char* _ensure(tstr_t p, int need) {
    char * nbuf;
    int nsize;
    if (!p || !p->str) {
        CERR("p:%p need:%d is error!", p, need);
        return NULL;
    }
    need += p->len;
    if (need <= p->size) //内存够用直接返回结果
        return p->str + p->len;
    nsize = _pow2gt(need);
    // 一定会成功, 否则一切都回归奇点
    nbuf = sm_malloc(nsize * sizeof(char));
    //这里复制内容
    memcpy(nbuf, p->str, p->size);
    sm_free(p->str);
    p->size = nsize;
    p->str = nbuf;
    return nbuf + p->len;
}

// 这里更新一下 当前字符串, 返回当前字符串的长度
static inline int _update(tstr_t p) {
    return (!p || !p->str) ? 0 : p->len + (int)strlen(p->str+p->len);
}

// 将item 中值转换成字符串 保存到p中
static char * _print_number(cjson_t item, tstr_t p) {
    char* str = NULL;
    double d = item->vd;
    int i = (int)d;
    
    if (d == 0) {  //普通0
        str = _ensure(p, 2);
        if (str)
            str[0] = '0', str[1] = '\0';
    }
    else if ((fabs(d - i)) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) {
        str = _ensure(p, 21); //int 值 
        if (str)
            sprintf(str, "%d", i);
    }
    else {
        str = _ensure(p, 64); //double值 
        if (str) {
            double nd = fabs(d); //得到正值开始比较
            if(fabs(floor(d) - d) <= DBL_EPSILON && nd < 1.0e60)
                sprintf(str, "%.0f", d);
            else if(nd < 1.0e-6 || nd > 1.0e9) //科学计数法
                sprintf(str, "%e", d);
            else
                sprintf(str, "%f", d);

        }
    }

    return str;
}

// 输出字符串内容
static char * _print_string(char * str, tstr_t p) {
    const char * ptr;
    char * nptr, * out;
    int len = 0, flag = 0;
    unsigned char c;

    if (!str || !*str) { //最特殊情况,什么都没有 返回NULL
        out = _ensure(p, 3);
        if (!out)
            return NULL;
        out[0] = '\"', out[1] = '\"', out[2] = '\0';
        return out;
    }

    for (ptr = str; (c=*ptr); ++ptr)
        flag |= ((c > 0 && c < 32) || c == '\"' || c == '\\');
    
    if (!flag) {  //没有特殊字符直接处理结果
        len = (int)(ptr - str);
        out = _ensure(p,len + 3);
        if (!out)
            return NULL;
        nptr = out;
        *nptr++ = '\"';
        strcpy(nptr, str);
        nptr[len] = '\"';
        nptr[len + 1] = '\0';
        return out;
    }

    //处理 存在 "和转义字符内容
    for (ptr = str; (c = *ptr) && ++len; ++ptr) {
        if (strchr("\"\\\b\f\n\r\t", c))
            ++len;
        else if (c < 32) //隐藏字符的处理, 这里可以改
            len += 5;
    }

    if ((out = _ensure(p, len + 3)) == NULL)
        return NULL;
    //先添加 \"
    nptr = out;
    *nptr++ = '\"';
    for (ptr = str; (c = *ptr); ++ptr) {
        if (c > 31 && c != '\"' && c != '\\') {
            *nptr++ = c;
            continue;
        }
        *nptr++ = '\\';
        switch (c){
        case '\\':    *nptr++ = '\\';    break;
        case '\"':    *nptr++ = '\"';    break;
        case '\b':    *nptr++ = 'b';    break;
        case '\f':    *nptr++ = 'f';    break;
        case '\n':    *nptr++ = 'n';    break;
        case '\r':    *nptr++ = 'r';    break;
        case '\t':    *nptr++ = 't';    break;
        default: sprintf(nptr, "u%04x", c);nptr += 5;    /* 不可见字符 采用 4字节字符编码 */
        }
    }
    *nptr++ = '\"';
    *nptr = '\0';
    return out;
}

//这里是 递归下降 的函数声明处, 分别是处理值, 数组, object
static char * _print_value(cjson_t item, tstr_t p);
static char * _print_array(cjson_t item, tstr_t p);
static char * _print_object(cjson_t item, tstr_t p);

// 定义实现部分, 内部私有函数 认为 item 和 p都是存在的
static char * _print_value(cjson_t item, tstr_t p) {
    char * out = NULL;
    switch ((item->type) & UCHAR_MAX) { // 0xff
    case _CJSON_FALSE: if ((out = _ensure(p, 6))) strcpy(out, "false"); break;
    case _CJSON_TRUE: if ((out = _ensure(p, 5))) strcpy(out, "true"); break;
    case _CJSON_NULL: if ((out = _ensure(p, 5))) strcpy(out, "null"); break;
    case _CJSON_NUMBER:    out = _print_number(item, p); break;
    case _CJSON_STRING:    out = _print_string(item->vs, p); break;
    case _CJSON_ARRAY:    out = _print_array(item, p); break;
    case _CJSON_OBJECT:    out = _print_object(item, p); break;
    }

    return out;
}

// 同样 假定 item 和 p都是存在且不为NULL
static char * _print_array(cjson_t item, tstr_t p)
{
    char * ptr;
    cjson_t child = item->child;
    int ncut, i;
    // 得到孩子结点的深度
    for (ncut = 0; (child); child = child->child)
        ++ncut;
    if (!ncut) { //没有孩子结点 直接空数组返回结果
        char* out = NULL;
        if (!(out = _ensure(p, 3))) 
            strcpy(out, "[]");
        return out;
    }

    i = p->len;
    if (!(ptr = _ensure(p, 1)))
        return NULL;
    *ptr = '[';
    ++p->len;
    for (child = item->child; (child); child = child->next) {
        _print_value(child, p);
        p->len = _update(p);
        if (child->next) {
            if (!(ptr = _ensure(p, 2)))
                return NULL;
            *ptr++ = ',';
            *ptr = '\0';
            p->len += 1;
        }
    }
    if (!(ptr = _ensure(p, 2)))
        return NULL;
    *ptr++ = ']';
    *ptr = '\0';
    return p->str + i;

}

// 同样 假定 item 和 p都是存在且不为NULL, 相信这些代码是安全的
static char * _print_object(cjson_t item, tstr_t p)
{
    char * ptr;
    int i, ncut, len;
    cjson_t child = item->child;

    // 得到孩子结点的深度
    for (ncut = 0; child; child = child->child)
        ++ncut;
    if (!ncut) {
        char* out = NULL;
        if (!(out = _ensure(p, 3)))
            strcpy(out, "{}");
        return out;
    }

    i = p->len;
    if (!(ptr = _ensure(p, 2)))
        return NULL;
    *ptr++ = '{';
    *ptr -= '\0';
    p->len += 1;
    // 根据子结点 处理
    for (child = item->child; (child); child = child->next) {
        _print_string(child->key, p);
        p->len = _update(p);

        //加入一个冒号
        if (!(ptr = _ensure(p, 1)))
            return NULL;
        *ptr++ = ':';
        p->len += 1;

        //继续打印一个值
        _print_value(child, p);
        p->len = _update(p);

        //结算最后内容
        len = child->next ? 1 : 0;
        if ((ptr = _ensure(p, len + 1)) == NULL)
            return NULL;
        if (child->next)
            *ptr++ = ',';
        *ptr = '\0';
        p->len += len;
    }
    if (!(ptr = _ensure(p, 2)))
        return NULL;
    *ptr++ = '}';
    *ptr = '\0';
    return p->str + i;
}

#define _INT_CJONSTR    (256)
/*
 *  这里是将 cjson_t item 转换成字符串内容,需要自己free
 * item        : cjson的具体结点
 *            : 返回生成的item的json串内容
 */
char * 
cjson_print(cjson_t item) {
    struct tstr p;
    char * out;
    if (NULL == item) {
        CERR("item is error = %p!", item);
        return NULL;
    }
    // 构建内存
    p.str = sm_malloc(sizeof(char) * _INT_CJONSTR);
    p.size = _INT_CJONSTR;
    p.len = 0;

    out = _print_value(item, &p); //从值处理开始, 返回最终结果
    if (out == NULL) {
        sm_free(p.str);
        CERR("_print_value item:%p, p:%p is error!", item, &p);
        return NULL;
    }
    return sm_realloc(out, strlen(out) + 1); // 体积变小 realloc返回一定成功
}

// --------------------------------- 下面是 cjson 输出部分的辅助代码 -----------------------------------------

/*
 * 创建一个bool的对象 b==0表示false,否则都是true, 需要自己释放 cjson_delete
 * b        : bool 值 最好是 _Bool
 *            : 返回 创建好的json 内容
 */
inline cjson_t
cjson_newnull() {
    cjson_t item = _cjson_new();
    item->type = _CJSON_NULL; 
    return item;
}

inline cjson_t
cjson_newbool(int b) {
    cjson_t item = _cjson_new();
    item->vd = item->type = b ? _CJSON_TRUE : _CJSON_FALSE; 
    return item;
}

inline cjson_t
cjson_newnumber(double vd)
{
    cjson_t item = _cjson_new();
    item->type = _CJSON_NUMBER;
    item->vd = vd;
    return item;
}

inline cjson_t
cjson_newstring(const char* vs)
{
    cjson_t item = _cjson_new();
    item->type = _CJSON_STRING;
    item->vs = tstr_dup(vs);
    return item;
}

inline cjson_t
cjson_newarray(void)
{
    cjson_t item = _cjson_new();
    item->type = _CJSON_ARRAY;
    return item;
}

inline cjson_t
cjson_newobject(void)
{
    cjson_t item = _cjson_new();
    item->type = _CJSON_OBJECT;
    return item;
}

/*
 * 按照类型,创建 对映类型的数组 cjson对象
 *目前支持 _CJSON_NULL _CJSON_BOOL/FALSE or TRUE , _CJSON_NUMBER, _CJSON_STRING
 * NULL => array 传入NULL, FALSE 使用char[],也可以传入NULL, NUMBER 只接受double, string 只接受char**
 * type        : 类型目前支持 上面几种类型
 * array    : 数组原始数据
 * len        : 数组中元素长度
 *            : 返回创建的数组对象
 */
cjson_t 
cjson_newtypearray(int type, const void * array, int len) {
    int i;
    cjson_t n = NULL, p = NULL, a;
    // _DEBUG 模式下简单检测一下
    DEBUG_CODE({
        if(type < _CJSON_FALSE || type > _CJSON_STRING || len <=0){
            CERR("check param is error! type = %d, len = %d.", type, len);
            return NULL;
        }
    });
    
    // 这里是实际执行代码
    a = cjson_newarray();
    for(i=0; i<len; ++i){
        switch(type){
        case _CJSON_NULL: n = cjson_newnull(); break;
        case _CJSON_FALSE: n = cjson_newbool(array? ((char*)array)[i] : 0); break;
        case _CJSON_TRUE: n = cjson_newbool(array? ((char*)array)[i] : 1); break;
        case _CJSON_NUMBER: n = cjson_newnumber(((double*)array)[i]); break;
        case _CJSON_STRING: n = cjson_newstring(((char**)array)[i]);break;
        }
        if(i){ //有你更好
            p->next = n;
            n->prev = p;
        }
        else
            a->child = n;
        p = n;
    }
    return a;
}

/*
 * 在array中分离第idx个索引项内容.
 * array    : 待处理的json_t 数组内容
 * idx        : 索引内容
 *            : 返回分离的json_t内容
 */
cjson_t 
cjson_detacharray(cjson_t array, int idx) {
    cjson_t c;
    DEBUG_CODE({
        if(!array || idx<0){
            CERR("check param is array:%p, idx:%d.", array, idx);
            return NULL;
        }
    });
    
    for(c=array->child; idx>0 && c; c = c->next)
        --idx;
    if(c>0){
        CERR("check param is too dig idx:sub %d.", idx);
        return NULL;
    }
    //这里开始拼接了
    if(c->prev)
        c->prev->next = c->next;
    if(c->next)
        c->next->prev = c->prev;
    if(c == array->child)
        array->child = c->next;
    c->prev = c->next = NULL;
    return c;
}

/*
 * 在object json 中分离 key 的项出去
 * object    : 待分离的对象主体内容
 * key        : 关联的键
 *            : 返回分离的 object中 key的项json_t
 */
cjson_t 
cjson_detachobject(cjson_t object, const char * key) {
    cjson_t c;
    DEBUG_CODE({
        if(!object || !object->child || !key || !*key){
            CERR("check param is object:%p, key:%s.", object, key);
            return NULL;
        }
    });
    
    for(c=object->child; c && tstr_icmp(c->key, key); c=c->next)
        ;
    if(!c) {
        CERR("check param key:%s => vlaue is empty.", key);
        return NULL;
    }
    if(c->prev)
        c->prev->next = c->next;
    if(c->next)
        c->next->prev = c->prev;
    if(c == object->child)
        object->child = c->next;
    c->prev = c->next = NULL;
    return c;
}
View Code

 

主要测试文件 test_cjson.c

#include <schead.h>
#include <scjson.h>

// 测试 cjson 函数
void test_readstr(void) {
    // 第二个 测试 json 串的解析
    puts("测试 cjson 是否可用");
    char text1[] = "{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\":       \"rect\", \n\"width\":      1920, \n\"height\":     1080, \n\"interlace\":  false,\"frame rate\": 24\n}\n}";
    TSTR_NEW(jstr1);
    jstr1->str = text1;
    cjson_t js = cjson_newtstr(jstr1);

    cjson_t name = cjson_getobject(js, "name");
    printf("name => %s\n", name->vs);

    cjson_t format = cjson_getobject(js, "format");
    printf("len(format) => %d\n", cjson_getlen(format));

    cjson_t interlace = cjson_getobject(format, "interlace");
    printf("interlace => %d\n", cjson_getint(interlace));

    cjson_delete(js);

    //进行第三组测试

    puts(" 测试 数组的读取");
    char text2[] = "[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]";
    TSTR_NEW(jstr2);
    jstr2->str = text2;
    js = cjson_newtstr(jstr2);
    int len = cjson_getlen(js);
    int i;
    for (i = 0; i < len; ++i) {
        cjson_t item = cjson_getarray(js,i);
        printf("%d => %s.\n", i, item->vs);
    }
    cjson_delete(js);


    puts("第三组测试");
    char text3[] = "[\n    [0, -1, 0],\n    [1, 0, 0],\n    [0, 0, 1]\n    ]\n";
    TSTR_NEW(jstr3);
    jstr3->str = text3;
    js = cjson_newtstr(jstr3);
    len = cjson_getlen(js);
    for (i = 0; i < len; ++i) {
        cjson_t item = cjson_getarray(js, i);
        printf("%d => %d.\n", i, cjson_getlen(item));
    }

    cjson_delete(js);
    exit(EXIT_SUCCESS);
}

/*
 * simple c 框架业务层启动的代码
 */
void test_readfile(void) {

    // 测试json解析结果是否正常
    cjson_t goods = cjson_newfile("./goods.json");

    // 数据输出
    int len = cjson_getlen(goods);
    printf("len = %d\n", len);

    // 打印其中一个数据
    int idx = len / 2;
    cjson_t jsidx = cjson_getarray(goods, idx);
    int ilen = cjson_getlen(jsidx);
    printf("ilen = %d\n", ilen);
    printf("[\"%s\", \"%s\", %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d]\n",
        cjson_getarray(jsidx, 0)->vs, 
        cjson_getarray(jsidx, 1)->vs,
        cjson_getint(cjson_getarray(jsidx, 2)),
        cjson_getint(cjson_getarray(jsidx, 3)),
        cjson_getint(cjson_getarray(jsidx, 4)),
        cjson_getint(cjson_getarray(jsidx, 5)),
        cjson_getint(cjson_getarray(jsidx, 6)),
        cjson_getint(cjson_getarray(jsidx, 7)),
        cjson_getint(cjson_getarray(jsidx, 8)),
        cjson_getint(cjson_getarray(jsidx, 9)),
        cjson_getint(cjson_getarray(jsidx, 10)),
        cjson_getint(cjson_getarray(jsidx, 11)),
        cjson_getint(cjson_getarray(jsidx, 12)),
        cjson_getint(cjson_getarray(jsidx, 13)),
        cjson_getint(cjson_getarray(jsidx, 14)),
        cjson_getint(cjson_getarray(jsidx, 15))
    );

    cjson_delete(goods);
    exit(EXIT_SUCCESS);
}

 

 最后链接过程, 编译文件 Makefile

CC = gcc
DEBUG = -ggdb3 -Wall -D_DEBUG
DIR = -I.
LIB = -lm
RUN = $(CC) $(DEBUG) -o $@ $^ $(LIB)
RUNO = $(CC) -c -o $@ $< $(DIR)
TEST = -nostartfiles -e $(*F)
RUNMAIN = $(RUN) $(TEST)

all:test_readstr.out test_readfile.out

# 库文件编译
%.o:%.c
    $(RUNO)

test_readstr.out:test_cjson.o scalloc.o tstr.o schead.o scjson.o
    $(RUNMAIN)

test_readfile.out:test_cjson.o scalloc.o tstr.o schead.o scjson.o
    $(RUNMAIN)

# 清除命令
.PHONY:clean
clean:
    rm -rf *.i *.s *.o *.out *~ ; ls

编译结果展示 

分别测试解析串和文件结果如下

还有解析goods.json 文件结果的

到这里基本测试完毕了, 这个scjson 引擎也可以收官''截稿'', 欢迎尝试, 也就1200行代码. 很实在, 应该好懂吧. 扯一点对于开发中编码问题, 推荐用UTF-8编码,

对于配置资源, 和操作系统原始编码保持一致.

文末分享一个BUG, 很有意思.  是标准函数是fgetc引起的

_Success_(return != EOF)
_Check_return_opt_
_ACRTIMP int __cdecl fgetc(
    _Inout_ FILE* _Stream
    );

 

当你用 int c = fgetc(txt) 的时候, c 总是 >=0 . 走 unsigend char 差不多. 因而造成了逻辑分支出错,  这里需要统一定义为 char c = fgetc(txt);

这个BUG在解析utf-8编码文件会遇到. 是不是很神奇. 切记不要趟坑.

 

后记 - OK

  错误是难免, 欢迎指正, 代码不好读, 说明你没有读过更加意外, 扯蛋的代码.

一个师傅三个徒弟 http://music.163.com/#/song?id=199768

 

posted on 2016-08-21 14:29  喜欢兰花山丘  阅读(753)  评论(1编辑  收藏  举报