1 /******************************************************************************
2 *
3 * parseConf(配置文件解析器)
4 *
5 * 1. 很多时候,我们安装一些软件,都可以通过改一些软件的配置文件来修改程序的
6 * 运行性能,如Tomcat修改端口号,访问数据库时一些固定的参数等等;
7 * 2. 本Demo就是干着这么一件事,从properties.conf文件中取出键值对(keyvalue),
8 * 实现更大程度提高代码的可利用性,通用性;
9 * 3. 以下是我们要解析的properties.conf文件中的内容:
10 * #title = charMaps
11 * t itle = c harMaps
12 * #jfdalj lasdfjl jflds
13 * jfdsljf
14 * =fjldsfsjd
15 * up = looking
16 * rows = 24 #jals djfaldjfals
17 * r ows = 25 #jals djfaldjfals
18 * c ols = 8 0
19 *
20 * = fsdfa
21 *
22 * c ols = 88 0
23 * jsflsjfd
24 * jfsldjaf
25 * tadjfsldjf=
26 *
27 * cols=88 0
28 * cols=888 0
29 * interval = 1 0000
30 * version = 11.0
31 * lkjk ng = i an f n ig
32 * test = 100000000
33 * 4. 这是我们使用本parseConf程序解析出来的结果:
34 * 001: t itle=c harMaps
35 * 002: up=looking
36 * 003: rows=24
37 * 004: r ows=25
38 * 005: c ols=88 0
39 * 006: cols=888 0
40 * 007: interval=1 0000
41 * 008: version=11.0
42 * 009: lkjk ng=i an f n ig
43 * 010: test=100000000
44 * 5. 配置文件的书写规范:
45 * 1. 键值对(keyvalue)以key=value的形式存在,等号两边可以出现空格;
46 * 2. 对于不能构成键值对(keyvalue)的key或value都会被忽略;
47 * 3. '#'为行注释符,目前只支持单行注释,不提供多行注释; :)
48 * 4. 如果解析中发现键值对中key相同,那么取最后那次的键值对为最终键值对;
49 * 6. 使用valgrind对程序进行内存释放检查结果,不会造成内存泄露:
50 * [user@localhost parseConf]$ valgrind ./parseConf properties.conf
51 * ==6325== Memcheck, a memory error detector
52 * ==6325== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
53 * ==6325== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
54 * ==6325== Command: ./parseConf properties.conf
55 * ==6325==
56 * ... //省略程序运行时的输出内容
57 * ==6325==
58 * ==6325== HEAP SUMMARY:
59 * ==6325== in use at exit: 0 bytes in 0 blocks
60 * ==6325== total heap usage: 39 allocs, 39 frees, 9,092 bytes allocated
61 * ==6325==
62 * ==6325== All heap blocks were freed -- no leaks are possible
63 * ==6325==
64 * ==6325== For counts of detected and suppressed errors, rerun with: -v
65 * ==6325== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 12 from 8)
66 *
67 * 2015-3-28 晴 深圳 曾剑锋
68 *****************************************************************************/
69
70 #include <stdlib.h>
71 #include <stdio.h>
72 #include <string.h>
73 #include <fcntl.h>
74
75 //默认一行数据的缓冲区大小
76 #define BUFFER_SIZE 1024
77
78 //键值对结构体,本Demo采用单链表来实现
79 typedef struct KVPAIR {
80 char key[128];
81 char value[512];
82 struct KVPAIR * next;
83 } kvpair;
84 /**
85 * 获取键值对的起始指针,参数是传入需要保存keyvalus首地址的指针,
86 * 函数返回值为0时表示获取成功
87 */
88 int getkvpairs(char *conffile, kvpair** kvpairs);
89 /**
90 * 通过key值获取kvpairs中的value,如果链表中没有key对应的数据,或者给的参数错误
91 * 将返回NULL
92 */
93 char* key2val(char* key, kvpair* kvpairs);
94 /**
95 * 通过value值获取kvpairs中的key,如果链表中没有value对应的数据,或者给的参数错误
96 * 将返回NULL
97 */
98 char* val2key(char* value, kvpair* kvpairs);
99 //打印输出kvpairs中所有的键值对
100 void printkvpairs(kvpair* kvpairs);
101 //用'\0'填充字符串
102 void cleanString(char* string);
103 /**
104 * 查看链表中有没有当前key对应的键值对,如果有,返回该key对应的键值对
105 * 如果没有,将返回NULL
106 */
107 kvpair* checkKey(char* key, kvpair* kvpairs);
108 //释放链表
109 int freekvpairs(kvpair* kvpairs);
110 //去除字符串左侧不可见字符
111 char *ltrim(char* str);
112 //去除字符串右侧不可见字符
113 char *rtrim(char* str);
114 //去除字符串左右不可见字符
115 char *trim(char* str);
116
117 /**
118 * on success, return 0, otherwise return -1
119 *
120 * 配置文件预处理过程是以一行一行来处理的,大致思路如下:
121 * while(直到文件末尾){
122 * 1.删除一行中前面的' ','\t';
123 * 2.忽略掉那些以'\n','#','='开头的行;
124 * 3.如果一行中有'#'注释,将'#'所在的位置设置'\0',代表字符串末尾;
125 * 也就是'#'以及后面注释都不管,因为那是注释 :)
126 * 4.删除一行中末尾的换行符;
127 * 5.修剪获取到的key,value字符串;
128 * 6.剩下的也就是是键值对了,保存在链表中.
129 * }
130 */
131 int getkvpairs(char* conffile, kvpair** kvpairs){
132 /**
133 * 如果传入的参数conffile不是NULL,并且配置文件能打开,则使用该文件中的配置参数
134 * 如果conffile指定的文件失效,则使用当前文件夹下的./properties.conf文件作为配置
135 * 文件,如果前面两者都失效,则会报错,并返回-1,文件后缀conf是properties的缩写
136 */
137 if(kvpairs == NULL){
138 perror("function( getkvpairs ) parameter ( kvpairs ) was NULL\n");
139 return -1;
140 }
141
142 if (conffile == NULL)
143 conffile = "./properties.conf";
144
145 FILE* conf = NULL;
146 conf = fopen(conffile, "r");
147 if(conf == NULL){
148 perror("function( getconfpairs ) can't found the properties file\n");
149 return -1;
150 }
151
152 int i = 0; //用于循环计数
153 int index = 0; //dealWithBuffer数组中作为保存缓存数据的指针
154 int length = 0; //保存字符串的长度
155 int equalIndex = 0; //保存等号的下标
156 kvpair* keyValueHead = NULL; //用于保存键值对的头节点
157 kvpair* currentkvpair = NULL; //用于保存键值对的当前节点
158 kvpair* previewkvpair = NULL; //用于保存键值对的前一个节点
159 char* lineBuffer = calloc(BUFFER_SIZE, sizeof(char));
160 char* dealWithBuffer = calloc(BUFFER_SIZE, sizeof(char));
161
162 while(fgets(lineBuffer, BUFFER_SIZE, conf)){
163 index = 0;
164 equalIndex = 0;
165 length = strlen(lineBuffer);
166 /**
167 * 删除行首的空格,制表符
168 */
169 for(i = 0; i < length; i++){
170 if((lineBuffer[i] != ' ') && (lineBuffer[i] != '\t')){
171 strcpy(dealWithBuffer, &(lineBuffer[i]));
172 break;
173 }
174 }
175 /**
176 * 清除一行中有#来注释的部分,保留键值对
177 * 且找出一行中=所在的位置,位置信息保存在equalIndex中
178 */
179 length = strlen(dealWithBuffer);
180 for(i = 0; i < length; i++){
181 if(dealWithBuffer[i] == '#' ){
182 dealWithBuffer[i++] = '\0';
183 break;
184 }else if(dealWithBuffer[i] == '=' ){
185 equalIndex = i;
186 }
187 }
188 /**
189 * 删除以换行符,#,=等字符开始的行,同时清空dealWithBuffer缓冲区
190 */
191 if((equalIndex == 0) || (lineBuffer[ 0 ] == '\n') || (lineBuffer[ 0 ] == '#')) {
192 /**
193 * 一定要记得清理这个缓存
194 */
195 cleanString(dealWithBuffer);
196 continue;
197 }
198 /**
199 * 如果一行数据末尾是'\n',则换成'\0',相当于移除'\n'
200 */
201 length = strlen(dealWithBuffer);
202 if(dealWithBuffer[length-1] == '\n'){
203 dealWithBuffer[length-1] = '\0';
204 }
205 /**
206 * 通过将'='换成'\0',这样就key,value字符串
207 */
208 dealWithBuffer[equalIndex] = '\0';
209 /**
210 * 一定要的得加1, 因为字符串长度不包括尾零
211 */
212 char* key = calloc(strlen(dealWithBuffer)+1, sizeof(char));
213 char* value = calloc(strlen(&(dealWithBuffer[equalIndex+1]))+1, sizeof(char));
214 strcpy(key, dealWithBuffer);
215 strcpy(value, &(dealWithBuffer[equalIndex+1]));
216
217 /**
218 * 修剪key,value的值,也就是去掉字符串左右两边的' ','\t'
219 */
220 trim(key);
221 trim(value);
222 /**
223 * 接下来检查key是否存在,如果存在,直接修改其value,而不创建数据结构体
224 * 如果key不存在,则创建结构体,保存key,value,加入链表
225 * 当然,先要保证key,value有效
226 */
227 if((strlen(key) != 0) && (strlen(value) != 0)){
228 if((currentkvpair = checkKey(key, keyValueHead)) != NULL){
229 bzero(currentkvpair->value, strlen(currentkvpair->value));
230 strcpy(currentkvpair->value, value);
231 }else{
232 currentkvpair = malloc(sizeof(kvpair));
233 strcpy(currentkvpair->key, key);
234 strcpy(currentkvpair->value, value);
235 currentkvpair->next = NULL;
236 if(keyValueHead == NULL){
237 keyValueHead = currentkvpair;
238 previewkvpair = currentkvpair;
239 }else {
240 previewkvpair->next = currentkvpair;
241 previewkvpair = currentkvpair;
242 currentkvpair = NULL;
243 }
244 }
245 }
246
247 bzero(dealWithBuffer, BUFFER_SIZE);//不能使用cleanString清理,因为字符中间有'\0'
248 cleanString(lineBuffer);
249 free(key);
250 free(value);
251 }
252 free(lineBuffer);
253 free(dealWithBuffer);
254 *kvpairs = keyValueHead;
255 fclose(conf);
256 return 0;
257 }
258
259 void cleanString(char* string){
260 int i;
261 int length = strlen(string);
262 for(i = 0; i < length; i++){
263 string[i] = '\0';
264 }
265 }
266
267 char* key2val(char* key, kvpair* kvpairs){
268 if((key == NULL) || (strlen(key) == 0)){
269 perror("function( key2val) parameter ( key ) was NULL\n");
270 return NULL;
271 }
272
273 kvpair* currentkvpair = kvpairs;
274 while(currentkvpair){
275 /**
276 * 本来打算直接用strcmp,但是貌似strcmp会自动比较字符串所占数组的大小
277 * 所以改成使用strncmp
278 */
279 if(strncmp(currentkvpair->key, key, strlen(key)) == 0){
280 return currentkvpair->value;
281 }
282 currentkvpair = currentkvpair->next;
283 }
284 return NULL;
285 }
286
287 char* val2key(char* value, kvpair* kvpairs){
288 if((value == NULL) || (strlen(value) == 0)){
289 perror("function( val2key) parameter ( value ) was NULL\n");
290 return NULL;
291 }
292
293 kvpair* currentkvpair = kvpairs;
294 while(currentkvpair){
295 if(strncmp(currentkvpair->value, value, strlen(value)) == 0){
296 return currentkvpair->key;
297 }
298 currentkvpair = currentkvpair->next;
299 }
300 return NULL;
301 }
302
303 kvpair* checkKey(char* key, kvpair* kvpairs){
304 if((key == NULL) || (strlen(key) == 0)){
305 perror("function( checkKey ) parameter ( key ) was NULL\n");
306 return NULL;
307 }
308
309 kvpair* currentkvpair = kvpairs;
310 while(currentkvpair){
311 if(strncmp(currentkvpair->key, key, strlen(key)) == 0){
312 return currentkvpair;
313 }
314 currentkvpair = currentkvpair->next;
315 }
316 return NULL;
317 }
318
319 void printkvpairs(kvpair* kvpairs){
320 if(kvpairs == NULL){
321 perror("function( printkvpairs ) parameter( kvpairs ) was NULL\n");
322 return;
323 }
324
325 int index = 1;
326 kvpair* currentkvpair = kvpairs;
327 printf("\033[32m--------------------------------------\033[0m\n");
328 while(currentkvpair){
329 printf("\033[32m %03d: %s=%s\033[0m\n", index, currentkvpair->key, currentkvpair->value);
330 currentkvpair = currentkvpair->next;
331 index++;
332 }
333 printf("\033[32m--------------------------------------\033[0m\n");
334 }
335
336 int freekvpairs(kvpair* kvpairs){
337 if(kvpairs == NULL){
338 return 0;
339 }
340
341 kvpair* previewkvpair = kvpairs;
342 kvpair* currentkvpair = kvpairs;
343 while(currentkvpair->next){
344 previewkvpair = currentkvpair;
345 currentkvpair = currentkvpair->next;
346 free(previewkvpair);
347 }
348 free(currentkvpair);
349 return 0;
350 }
351
352 char *ltrim(char* str) {
353 char str_tmp[BUFFER_SIZE] = {0};
354 char *current = str_tmp;
355 int count = 0;
356
357 strncpy(str_tmp, str, strlen(str));
358 bzero(str, strlen(str));
359
360 while(' ' == (*current) || ('\t' == *current ))
361 current++;
362
363 strncpy(str, current, strlen(current));
364 return str;
365 }
366
367 char *rtrim(char* str) {
368 int count = 0;
369 int i = strlen(str)-1;
370 for(; i >= 0; i--){
371 if((' ' == str[i]) || ('\t' == str[i]) || ('\0' == str[i]))
372 str[i] = '\0';
373 else
374 break;
375 }
376 return str;
377 }
378
379 char *trim(char* str) {
380 return rtrim(ltrim(str));
381 }
382
383 int main(int argc, char* argv[]){
384 //传入需要被解析的文件
385 if(argc < 2){
386 printf(" Usage:\n\r ./parseConf <configure file> \n\n");
387 return ;
388 }
389
390 /**
391 * 获取键值对,键值对头节点保存在keyValues中
392 */
393 kvpair* keyValues;
394 getkvpairs(argv[1], &keyValues);
395 printf("\n\033[32m\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\033[36mDemo\033[32m/////////////////\033[0m\n");
396
397 /**
398 * 将配置文件中的内容打印出来
399 */
400 int fd = open(argv[1], O_RDONLY);
401 if(-1 == fd){
402 perror("open file error");
403 }
404
405 char buffer[1024] = {0};
406 read(fd, buffer, 1024);
407 printf("%s\n", buffer);
408
409 close(fd);
410
411 /**
412 * 将当前的所有的键值对打印出来
413 */
414 printkvpairs(keyValues);
415 /**
416 * 通过key获取value值
417 */
418 char* key = "rows";
419 printf("\033[32mgetValueBykey:key = %s; value = %s\033[0m\n", key, key2val(key, keyValues));
420 /**
421 * 通过value获取key值
422 */
423 char* value = "24";
424 printf("\033[32mgetKeyByValue:value = %s; key = %s\033[0m\n", value, val2key(value, keyValues));
425 printf("\033[32m--------------------------------------\033[0m\n");
426 /**
427 * 释放keyValues链表
428 */
429 if(freekvpairs(keyValues) == 0){
430 printf("\033[32m Memory of keyValues linked has freed\033[0m\n");
431 printf("\033[32m--------------------------------------\033[0m\n");
432 }
433 }