c印记(七): ini file解析
来源:互联网 发布:数控编程毕业论文 编辑:程序博客网 时间:2024/04/28 23:24
目录
- 目录
- 一 写在前面的话
- 二 ini文件格式
- 简介
- 格式
- 三ini文件解析 API
- 声明基础数据类型
- API 声明
- 四ini 文件解析API实现
- ini文件解析iniFileParseFile
- 行解析iniFileParseLine
- 节名解析iniFileParseSection
- 键-值 参数对解析iniFileParseParameter
- 五使用例子
- 六完整源代码
一、 写在前面的话
ini 文件并不是什么新东西,所以解析ini文件的算法实现也不少,这里写一个实现也基本上算是造轮子了,但换个角度看,更准确一点的说法应该是深度定制数据自己的轮子,因为网上虽算法不少,但要么就是过于复杂,喜欢简洁的我是无法忍受的,除了复杂的版本之外,还有一个极端,就是过度简洁,要么是需要的API没有,要么就是解析的不够完善,于是乎就萌生了自己写一个的想法,目前还只有解析读取的功能,并没有写入和输出ini文件的功能(就目前来说,还用不到那些,为了简洁,就暂未实作)
二、 ini文件格式
1. 简介
ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式 ,当然这类文件也可以用来初始化或配置其他的应用程序(不管是windows,linux还是android系统),因为它比较简单,没有xml那么复杂和庞大,不过它也有缺点,就是能够表达的内容相对单一,无法向xml可以嵌套语句,但就一般的简单配置数据而已,ini文件已经足够了。
2. 格式
ini文件一般是由节, 键,值组成,其中节并不是必须的,一个ini文件可以只由(键,值)这样的参数对组成。
节 [section]参数(键=值) key=value注解
注解使用分号表示(;)。 在分号后面的文字,直到该行结尾都全部为注解。除此之外,本ini文件解析 算法还增加了类似 shell脚本中的注释符号(#)。
; 这是一个注释# 这是一个注释
- 节,
ini文件中节 可以用来表示某一个项目的参数,或者某一类型,某一组参数,一般发生在多个项目共用一个ini文件,或者一个项目中有不同种类的参数(如,各个模块有各个模块的参数)。 一般来说这种节 的名字不应该重复,在本文的算法实现中也是不支持 重复节名字的。
- 键-值 参数对
可以重复的是 键-值 这样的参数对,比如,ini文件中有一个节表示项目需要的插件列表,就可以以如下的方式表示:
[plugin.list]plugin = liba.soplugin = libb.soplugin = libc.so
- 注解
在本文算法实现中,即支持注解独占一行,也支持在键-值 参数对之后,形如:
;显示模块配置[display.cfg]#控制显示开关enable_display=true ;true:开启显示,false:关闭显示enable_hdmi = true #true: 开启hdmi,false:关闭hdmi
- 空格
在本文的实现中,支持 键-值 之间存在 空格,值 和 行末尾注释之间存在空格。
三、ini文件解析 API
1. 声明基础数据类型
在本问的实现中,并没有有直接使用 int,unsigned int之类的基本数据类型,均使用typedef进行了 重定义,以便类型名字简洁,长短统一,在使用的时候能够对齐,这样看起来更美观一些。
//general signed integer typetypedef int GS32; //32 bits//general unsigned integer typetypedef unsigned int GU32; //32 bits
如上面所示,基础数据类型的定义,都是以 ‘G’开始的,表示通用(general),然后跟着的是 S(有符号)或U(无符号),最后是数据长度。这里指定义了 32位的有符号和无符号类型,因为本文当中只使用到这两个数据类型,当然,还有 void,和 char,这两个也可以进行重定义,但因为其本来就是四个字符宽度了,所以本文实现中并未对其进行重定义。除此之外,其他的基础数据类型也可以以此方式进行重定义,如 8位无符号,可重定义为 GU08,16位有符号,可重定义为 GU16,float,可重定义为 GFLT等。
因为本文是使用c语言实现的解析算法,所以需要定义 布尔 类型的基础数据类型。
//boolean type declarationtypedef enum GBOL{ GFALSE = 0, GTRUE = !GFALSE,}GBOL;
除了基础数据类型之外,为了更好的调试或遇到异常时,能更好的知道异常原因,所以需要定义错误类型。
//general error type definetypedef enum general_error_e{ G_OK = 0, /** There were insufficient resources to perform the requested operation */ G_ErrorInsufficientResources = (GS32) 0x80001000, /** There was an error, but the cause of the error could not be determined */ G_ErrorUndefined = (GS32) 0x80001001, /** One or more parameters were not valid */ G_ErrorBadParameter = (GS32)0x80001004, G_ErrorInvalidOperation = (GS32)0x8000101C, /** invalid operation */ /** No target with the specified name string was found */ G_ErrorNotFound = (GS32)0x80001003,}general_error_t;
2. API 声明
这里就不挨个介绍API了,直接将将整个 头文件的实现都贴出来。
#ifndef __TINY_INI_FILE_H__#define __TINY_INI_FILE_H__#ifdef __cplusplusextern "C"{#endif/** * ini file * syntax: * [] < section lable,eg. [log.conf] * # < one line comment * ; < one line comment * = < link key(at left) and value(at right),eg. level = debug * * @note 1. the key in a section is not unique, it mean that, maybe have * several same key in one section. * * for example: * * [log.conf] #log print configure * level = debug * enable_line_number = true * * ;pluing list * [plugin.list] * plugin = libfdk_aac_enc.so * plugin = libx264_enc.so * *//*************************************************************************** * * macro declaration * ***************************************************************************//*** if ini file havan't any section,then parser will add a default section* user cann't use this section name,it is reserve section name.*/#define DEFAULT_SECTION_NAME "default"#define SECTION_MAX_COUNT 32 /** maximu section count in one ini file */#define LINE_BUF_MAX_LEN 1024 /** the expression line maximum length *//** compat the keyword 'inline' */#if (_MSC_VER > 1200) && !defined (__cplusplus)#define inline _inline /** to compat keyword 'inline' */#endif/*************************************************************************** * * data structure declaration * ***************************************************************************//** base data type define *///general signed integer typetypedef int GS32; //32 bits//general unsigned integer typetypedef unsigned int GU32; //32 bits//boolean type declarationtypedef enum GBOL{ GFALSE = 0, GTRUE = !GFALSE,}GBOL;//general error type definetypedef enum general_error_e{ G_OK = 0, /** There were insufficient resources to perform the requested operation */ G_ErrorInsufficientResources = (GS32) 0x80001000, /** There was an error, but the cause of the error could not be determined */ G_ErrorUndefined = (GS32) 0x80001001, /** One or more parameters were not valid */ G_ErrorBadParameter = (GS32)0x80001004, G_ErrorInvalidOperation = (GS32)0x8000101C, /** invalid operation */ /** No target with the specified name string was found */ G_ErrorNotFound = (GS32)0x80001003,}general_error_t;/** ini file data structure define */typedef struct key_value_pair_s key_value_pair_t;typedef struct ini_section_s //一个节的数据结构声明{ int pair_count; /**the conut of pairs in current section */ key_value_pair_t* pairs; char *name; /** section name */ void* opaque; /** private data, user cann't modify it */}ini_section_t;/** todo, add more function, for example add key-value, add section, save ini */typedef struct tiny_ini_file_s //ini 文件解析的API声明{ GBOL is_loaded; /** GTRUE: already loaded one file, GFALSE: doesn't */ void* opaque; /* *@brief load a ini file * *@param ini_file [in] ini file instance pointer *@param file_name [in] the name of ini file * *@return sucess: FRE_OK, fail: error code. * **/ //加载一个ini文件 GS32(*load)(struct tiny_ini_file_s* ini_file, const char* file_name); /* *@brief get the value of a key which is first key in the section. * *@param ini_file [in] ini file instance pointer *@param section [in] the name of section *@param key [in] the name of key *@param value [out] the value of the key * *@return sucess: FRE_OK, fail: error code. * *@note maybe have many same key in one section, here just get we find * the first value with "key" * **/ //获取一个 键 对应的 值 GS32(*getValue)(struct tiny_ini_file_s* ini_file, const char* section, const char* key, char** value); /* *@brief get all values of a key in the section. * *@param ini_file [in] ini file instance pointer *@param section [in] the name of section *@param key [in] the name of key *@param values [out] the all values of the key *@param count [out] the count of values * *@return sucess: FRE_OK, fail: error code. * *@note * **/ //获取一个键 对应的所有值(主要是针对在一个节中有多个相同 键 名的参数对) GS32(*getValues)(struct tiny_ini_file_s* ini_file, const char* section, const char* key, char** values[], GU32* count); /* *@brief free values * *@param ini_file [in] ini file instance pointer *@param values [in] the values which need to free *@param count [in] the count of values * *@return none. * *@note * **/ //释放获取到的键-值 参数对, 对应 getValues void(*freeValues)(struct tiny_ini_file_s* ini_file, char* values[], GU32 count); /* *@brief get a section with section name. * *@param ini_file [in] ini file instance pointer *@param name [in] the name of section *@param key [in] the name of key *@param section [out] required section pointer * *@return sucess: FRE_OK, fail: error code. * *@note * **/ //获取一个节 GS32(*getSection)(struct tiny_ini_file_s* ini_file, const char* name, ini_section_t** section); /* *@brief detect if there is a key in the current section * *@param ini_file [in] ini file instance pointer *@param section [in] the name of section *@param key [in] the name of key * *@return have: GTRUE, haven't: GFALSE * *@note * **/ //判断ini文件中是否存在某个 键 GBOL(*hasKey)(struct tiny_ini_file_s* ini_file, const char* section, const char* key); /* *@brief detect if there is a section * *@param ini_file [in] ini file instance pointer *@param section [in] the name of section * *@return have: GTRUE, haven't: GFALSE * *@note * **/ //判断ini文件中是否存在某个 节 GBOL(*hasSection)(struct tiny_ini_file_s* ini_file, const char* section); /* *@brief print all data in ini file * *@param ini_file [in] ini file instance pointer * *@return none * *@note * **/ //打印ini文件中的所有内容(除了注解之外) void(*dump)(struct tiny_ini_file_s* ini_file); /* *@brief release ini parser * *@param ini_file [in] ini file instance pointer * *@return none. * *@note * **/ //销毁ini 文件解析API实例 void(*destroy)(struct tiny_ini_file_s* ini_file);}tiny_ini_file_t;/*************************************************************************** * * API declaration * ***************************************************************************///创建一个ini文件解析API的实例GS32 TinyIniFileCreate(tiny_ini_file_t** ini_file);#ifdef __cplusplus}#endif#endif //end of __TINY_INI_FILE_H__
- 注意
如上所示,可以看出ini文件的API都是在一个结构体中的函数指针, 之所以如此声明,其一,仿c++的类的继承和多态中的虚基类,在必要的时候完全可以重新实现这些API,其二,以这样的形式声明,使用者和实现者之间不必有编译上的依赖,比如,library core当中是一些基础功能实现的library(其中包含 ini解析),有一个library a当中,需要读取ini配置文件中的某些参数,这个时候,可以在host executable Application中创建一个ini 文件解析API的实例,然后透过load() API 去加载目标ini文件,成功之后,再透过library a的某个API(eg. a_init(tiny_ini_file_t* ini_file) ),传入到library a当中,在这个例子中,在编译阶段library a就可以完全不依赖 library core, 只需要依赖头文件 tiny_ini_file.h。
四、ini 文件解析API实现
这里主要说明其中解析过程中的几个核心函数,
1. ini文件解析:iniFileParseFile
ini file的API,load() 前半部分就只判断参数,以及打开传入的ini 文件,没什么好说的,这里要说 的第一个函数就是 iniFileParseFile,这是ini 文件解析的最顶层的函数,被 API load()调用。
函数声明为:
\\mif:ini文件API的数据结构指针\\fp: 已经打开了一个ini文件的文件指针\\成功:返回 G_OK, 失败: 返回相应的错误号static GS32 iniFileParseFile(my_ini_file_t* mif, FILE* fp);
函数实现为:
static GS32 iniFileParseFile(my_ini_file_t* mif, FILE* fp){ GS32 ret = G_OK; /**表示 ini文件中的行号,解析一行,就自加一次,当ini文件有错时可以用以定位 */ GU32 line_num = 0; /**暂存一行数据的buffer,其中LINE_BUF_MAX_LEN 在tiny_ini_file.h中定义 */ char line_buf[LINE_BUF_MAX_LEN] = { 0 }; while (fgets(line_buf, LINE_BUF_MAX_LEN, fp)) /** 从文件中读取一行数据 */ { line_num++; /**因为行号从 1 开始,故在这里先自加,然后再解析 */ /** 解析行 */ ret = iniFileParseLine(mif, line_buf, strlen(line_buf), line_num); if (G_OK != ret) { LOGE("parse line failed\n"); break; } } return ret;}
2. 行解析:iniFileParseLine
函数声明为:
\\mif: ini文件API的数据结构指针\\line:一行数据的起始指针\\length:行的长度\\line_num:行号\\成功: 返回G_OK, 失败:返回对应的错误号static GS32 iniFileParseLine(my_ini_file_t* mif, char* line, GU32 length, GU32 line_num);
函数的实现为:
static GS32 iniFileParseLine(my_ini_file_t* mif, char* line, GU32 length, GU32 line_num){ char* ptr = line; char* pEnd = line + length; GS32 ret = G_OK; while (ptr < pEnd) { /**跳过行首的 white space */ while (isspace(*ptr)) ptr++; /** skip white-space */ char ch = *ptr; if (isCommentTag(ch) == GTRUE) {/** 判断是否为 注解 起始关键字(';'或'#'),如果是,就直接跳过这一整行 */ /** skip component */ ptr = pEnd; } else if (ch == '[') {/** 判断是否为 节 的起始关键字符,如果是解析 节,主要是解析节的名字 */ /** ++ indicate skip character '[' */ ptr = iniFileParseSection(mif, ++ptr, pEnd, line_num, &ret); } else if (ch == '\0') /** line end */ {/** 如果是遇到'\0',表示行结束,什么也不用做 */ /**do nothing */ } else { /** 这里就是开始解析 参数 对 */ if (mif->section_count == 0) {/**如果在解析第一个参数对的时候,还没有遇到 节 ,那有可能就是没有节或者, 后面才有节,所以在这里将创建一个默认的节来包含所有不在节范围来的参数对。其中 DEFAULT_SECTION_NAME是在头文件中定义的,需要说明的是:不能在ini文件中出现和默认节名相同的节名,这是不被允许的。 */ /** alloc default section */ ini_section_t* isec = iniFileNewSection(DEFAULT_SECTION_NAME); if (isec) { /** 将节添加到节列表中 */ ret = iniFileAddSection(mif, isec); if (G_OK != ret) { LOGE("add section to list failed(0x%08x)\n", ret); free(isec); break; } } else { ret = G_ErrorInsufficientResources; break; } } /** 解析参数对,其中iniFileGetNewestSection(mif),表示参数对,都是解析到就近节当中*/ /** * current section aways is the newest section ? * TODO, this case is aways right? */ ptr = iniFileParseParameter(iniFileGetNewestSection(mif), ptr, pEnd, line_num, &ret); } } return ret;}
3. 节(名)解析:iniFileParseSection
函数声明为:
\\mif: ini文件API的数据结构指针\\line:一行数据的起始指针\\line_end:一行数据的末尾指针\\line_num:行号\\ret:函数的返回值,成功:G_OK。失败:错误号\\函数返回 节解析之后的,行数据指针,如 == line_end,表示当前行解析结束static char* iniFileParseSection(my_ini_file_t* mif, char* line, char* line_end, GU32 line_num, GS32* ret);
函数实现:
static char* iniFileParseSection(my_ini_file_t* mif, char* line, char* line_end, GU32 line_num, GS32* ret){ char* ptr = line; char* pEnd = line_end; char* section = strchr(ptr, ']'); /**寻找结束标识符 */ /** process section name */ if (section) /**不为空,表示是有效的节,为空,语法错误,将结束解析 */ { //char temp = *section; //*section = '\0'; /** 判断列表中是否已经存在当前节,如果存在,语法错误,因为语法规定不能存在名字相同的节 */ if (iniFileHasSection_l(mif, ptr) == GTRUE) { LOGE("syntax error[line:%d]: section(%s) already exist, end parse \n", line_num, ptr); ptr = pEnd; *ret = G_ErrorUndefined; } else { char* pLeft = ptr; char* pRight = section - 1; /** trim left white-space */ while (isspace(*pLeft) && *pLeft) pLeft++; /** trim right white-space */ while (isspace(*pRight) && pRight >= pLeft) pRight--; *(pRight + 1) = '\0'; //创建 节 ini_section_t* isec = iniFileNewSection(pLeft); if (isec) { //将节加入到列表当中 GS32 result = iniFileAddSection(mif, isec); if (G_OK == result) { ptr = section + 1; /** + 1 indicate skip character ']' */ } else { free(isec); /** release resource */ LOGE("add secition(%s) to list failed(0x%08x)\n", pLeft, *ret); ptr = pEnd; *ret = result; } } else { LOGE("create section(%s) failed\n", pLeft); ptr = pEnd; *ret = G_ErrorInsufficientResources; } } } else { LOGE("syntax error[line:%d]: section doesn't end with ']'\n", line_num); ptr = pEnd; *ret = G_ErrorUndefined; } return ptr;}
4. 键-值 参数对解析:iniFileParseParameter
函数声明为:
\\mif: ini文件API的数据结构指针\\line:一行数据的起始指针\\line_end:一行数据的末尾指针\\line_num:行号\\ret:函数的返回值,成功:G_OK。失败:错误号\\函数返回 节解析之后的,行数据指针,如 == line_end,表示当前行解析结束static char* iniFileParseParameter(ini_section_t* section, char* line, char* line_end,GU32 line_num, GS32* ret)
函数实现为:
static char* iniFileParseParameter(ini_section_t* section, char* line, char* line_end,GU32 line_num, GS32* ret){ char* ptr = line; char* pEnd = line_end; char* key_end = strchr(ptr, '='); /**寻找 参数对中 键与值之间的各个标识符 '=' */ if (key_end)/** 如果存在,就分别解析'='两边的键和值 */ { char* pRight = key_end - 1; /**去除,键 与 '=' 之间的white-space */ /** strip right white-space */ while (isspace(*pRight) && pRight >= ptr) pRight--; GU32 key_len = (pRight + 1) - ptr; char* key = ptr; /** 获取键的起始指针以及长度 */ char* value = key_end + 1; /** + 1 indicate skip character '=' */ /** 去除 值 与 '=' 之间的white-space */ /** strip left white-space */ while (isspace(*value) && value < pEnd) value++; if (value < pEnd) /** 如果值的指针 < line end,表示可能有有效的 值,否则语法错误 */ { /** find value end */ char* value_end = value; /** 寻找 值的 结束位置,条件是遇到 white-space或者 注解标识符或者line end就截止 */ /** strip ritght white-space and comment after the value of parameter */ while ((!isspace(*value_end) && (isCommentTag(*value_end) == GFALSE)) && (value_end < pEnd)) { value_end++; } if (value_end > value) {/** 如果 值的 结束位置 > 值的起始位置,表示有有效的 值 */ GU32 value_len = value_end - value; /** 创建一个键-值参数对的item */ key_value_pair_t* pair = iniFileNewParameter(key, key_len, value, value_len); if (pair) { /** 将键-值 参数对加入到列表中 */ iniFileAddParameter(section, pair); ptr = value_end; } else { ptr = pEnd; *ret = G_ErrorInsufficientResources; } } else { LOGE("syntax error: the value of parameter is empty(right of \'=\')\n"); ptr = pEnd; *ret = G_ErrorUndefined; } } else { LOGE("syntax error[line:%d]: the value of parameter is empty(right of \'=\')\n", line_num); ptr = pEnd; *ret = G_ErrorUndefined; } } else { LOGE("syntax error[line:%d]: not complete key, doesn't find end character \'=\' \n", line_num); ptr = pEnd; *ret = G_ErrorUndefined; } return ptr;}
五、使用例子
- 建立一个名为my_test.ini的ini文件,其内容如下:
#这是一个测试用的ini文件;这个ini文件包含默认 节,自定义节,参数对等app_name = ini_file_parse_test #程序的名字app_version = 0.0.1 ;程序版本[test_section]test_key = test_value1test_key = test_value2other_key= something_elsetest_key = test_value3test_key = test_value4
- 编写测试程序
#include <stdio.h>#include "tiny_ini_file.h"#define LOGD printf#define LOGE printf#define TEST_INI_FILE_NAME "my_test.ini"int main(int argc, char *argv[]){ GS32 ret = G_OK; tiny_ini_file_t* ini_file = NULL; ret = TinyIniFileCreate(&ini_file); if (G_OK == ret) { ret = ini_file->load(ini_file, TEST_INI_FILE_NAME); if (G_OK == ret) { char* value = NULL; ini_file->dump(ini_file); /** print the content of ini file */ LOGD("=========this is cut-off rule===============\n\n"); LOGD("start test get value and get values:\n\n"); ret = ini_file->getValue(ini_file, DEFAULT_SECTION_NAME, "app_name", &value); if (G_OK == ret) { LOGD("section: %s, key: %s, value:%s\n", DEFAULT_SECTION_NAME, "app_name", value); } { GU32 i; char** values = NULL; GU32 values_cnt = 0; ret = ini_file->getValues(ini_file, "test_section", "test_key", &values, &values_cnt); if (G_OK == ret) { for (i = 0; i < values_cnt; i++) { LOGD("section: %s, key: %s, value[%d]:%s\n", "test_section", "test_key", i, values[i]); } /** when use up, valuse, need do free with API freeValues() */ ini_file->freeValues(ini_file, values, values_cnt); } } } else { LOGE("load ini file(%s) failed(0x%08x)\n", TEST_INI_FILE_NAME, ret); } } else { LOGE("create ini file instance failed(0x%08x)\n", ret); } if (ini_file) { ini_file->destroy(ini_file); } return 0;}
六、完整源代码
https://git.oschina.net/xunawolanxue.com/Tiny-ini-file-parser
- c印记(七): ini file解析
- C#.Net ini file 操作
- c印记(三): my_oopc
- c印记(四): 递归
- c印记(五):数组
- ini解析库 c语言
- C语言解析Ini格式文件
- INI file
- 使用C语言解析INI文件
- c 语言 解析ini文件为xml
- ini文件解析c库(iniparser)
- 纯C语言INI文件解析
- 纯C语言INI文件解析
- C语言实现ini解析函数 getPrivateProfileString
- 纯C语言INI文件解析
- ini文件解析c库(iniparser)
- c印记(一):面向对象
- c印记(二):lw_oopc简介
- Toast原理解析
- 浅谈new、delete
- 【ZJOI2002】昂贵的聘礼
- 支付宝TCC模型
- 将文件间的编译依存关系降至最低(第二部分)
- c印记(七): ini file解析
- 数据库事务 ACID
- html5离线存储
- Windows 常用软件清单
- 简单工厂模式
- 黑白棋
- React-Native环境搭建(Windows for Android)
- 让Safari中收藏的个人网站显示Logo
- 自定义输入对话框,调用者决定对话框按钮的功能