Этот пост, для тех, кто пишет на C[/C++]. Остальные могут не читать.
Как всегда, работая над проектами хочу поделится очередной технологией. Наверное, громко сказано. Скорее, простым решением в области создания и работы с файлом настройки программ.
В мире много готовых решений. От XML-формата до… Одним словом, много. Данная статья не претендует на что-то сверхновое, не побуждает к дискуссиям о стиле, методах и реализации.
Я, просто, делюсь быстрым решением, как читать параметры и значения, разделённые знаками «равно» из файла настроек формата, похожего на, скажем, php.ini
Я — лентяй. Мне чертовски лениво тратить своё время на разбор XML, искать для этого инструментарий, изучать возможности различных IDE… Мне проще сесть и сделать самому так, как нравится, так, как я считаю проще. Многолетние применение файлов настроек типа <параметр> = <значение> для UNIX-подобных ОС оправдывает мой выбор.
Да, я неохотно пишу на «двух плюсах».
И я не буду вступать в дискуссии в ленте комментариев. Я сниму «галочки» с предложений отслеживания сообщений для данного поста.
Если вам понравилось, забирайте. Получиться (есть) лучше, посмотрю, спасибо.
Цель — минимизировать использование дополнительных библиотек, кроме stdlib.
Описание данного метода является частью создаваемой библиотеки для личных целей.
Итак, определимся с исходными данными и настройками.
Основные идеи по определению макросов я «слямзил» у OpenCV.
Теперь укажем функции подготовки данных и различные сопутствующие утилиты: файл
Как старый склеротик с диагнозом «создал — оттестировал — работает — забыл» всегда пишу комментарии. Думаю, с описаниями функций всё ясно.
И основное объявление данных и методов конкретно для конфигурационного файла пропишем в
Видим, что есть лишь две функции для открытия и инициализации структуры параметров и закрытия с очисткой памяти.
Возможно, ваши вкусы позволят добавить к перечислению
В теле функции
Мне было лень.
Вот, и всё. :)
Я тспользую для работы на С IDE NetBeans. Почему? Нравится! :)
В данной среде для построения тестов применяю Framework CUnit.
Утилита
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Test: test1…
Проверка.
Строка " test1". Результат: «test1»
Строка " test 2". Результат: «test 2»
Строка " !23test3". Результат: "!23test3"
passed
suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a
Elapsed time = 0.000 seconds
Утилита
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Test: test1…
Проверка.
Строка «test1». Результат: «test1»
Строка «test 2 ». Результат: «test 2»
Строка "!23test3# ". Результат: "!23test3#"
passed
suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a
Elapsed time = 0.000 seconds
Утилита
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 0xffff
Ваша строка: «0xffff»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 65535, Система исчисления = 16
passed
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a
Elapsed time = 0.000 seconds
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 1277o
Ваша строка: «1277o»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 703, Система исчисления = 8
passed
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a
Elapsed time = 0.000 seconds
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 4ret
Ваша строка: «4ret»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 0, Система исчисления = 0
FAILED
1. tests/is_digital_test.c:59 — (i_result = is_digital(c_test, &ll_digit, &i_base)) == 1
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 1 1 0
asserts 2 2 1 1 n/a
Elapsed time = 0.000 seconds
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 1024
Ваша строка: «1024»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 1024, Система исчисления = 10
passed
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a
Elapsed time = 0.000 seconds
Файл:
Всех благ!
Как всегда, работая над проектами хочу поделится очередной технологией. Наверное, громко сказано. Скорее, простым решением в области создания и работы с файлом настройки программ.
В мире много готовых решений. От XML-формата до… Одним словом, много. Данная статья не претендует на что-то сверхновое, не побуждает к дискуссиям о стиле, методах и реализации.
Я, просто, делюсь быстрым решением, как читать параметры и значения, разделённые знаками «равно» из файла настроек формата, похожего на, скажем, php.ini
Немного лирики.
Я — лентяй. Мне чертовски лениво тратить своё время на разбор XML, искать для этого инструментарий, изучать возможности различных IDE… Мне проще сесть и сделать самому так, как нравится, так, как я считаю проще. Многолетние применение файлов настроек типа <параметр> = <значение> для UNIX-подобных ОС оправдывает мой выбор.
Да, я неохотно пишу на «двух плюсах».
И я не буду вступать в дискуссии в ленте комментариев. Я сниму «галочки» с предложений отслеживания сообщений для данного поста.
Если вам понравилось, забирайте. Получиться (есть) лучше, посмотрю, спасибо.
Цель — минимизировать использование дополнительных библиотек, кроме stdlib.
Побольше практики.
Описание данного метода является частью создаваемой библиотеки для личных целей.
Итак, определимся с исходными данными и настройками.
cs_types.h
#ifndef CS_TYPES_H #define CS_TYPES_H #if defined WIN32 || defined _WIN32 # define CS_CDECL __cdecl # define CS_STDCALL __stdcall #else # define CS_CDECL # define CS_STDCALL #endif #ifndef CS_EXTERN_C_FUNC_PTR # ifdef __cplusplus # define CS_EXTERN_C_FUNC_PTR(x) extern "C" { typedef x; } # else # define CS_EXTERN_C_FUNC_PTR(x) typedef x # endif #endif #ifndef CS_INLINE # if defined __cplusplus # define CS_INLINE inline # elif (defined WIN32 || defined _WIN32 || defined WINCE) && !defined __GNUC__ # define CS_INLINE __inline # else # define CS_INLINE static # endif #endif /* CS_INLINE */ #ifdef __cplusplus extern "C" { #endif #if (defined WIN32 || defined _WIN32 || defined WINCE) && defined CSAPI_EXPORTS # define CS_EXPORTS __declspec(dllexport) #else # define CS_EXPORTS #endif #ifndef CS_API # define CS_API(rettype) CS_EXPORTS rettype CS_CDECL #endif #ifdef __cplusplus } #endif //#define DEBUG_MODE #ifdef DEBUG_MODE static short debug_level = 3; #endif #endif /* CS_TYPES_H */
Основные идеи по определению макросов я «слямзил» у OpenCV.
Теперь укажем функции подготовки данных и различные сопутствующие утилиты: файл
cs_common.h
#ifndef SC_COMMON_H #define SC_COMMON_H #include "cs_types.h" #include <getopt.h> /*----------------------------------------------------------------------------*/ /** * @typedef opt_t * @brief Приведённый тип структуры struct option (@see man 3 getopt) */ typedef struct option opt_t; /*----------------------------------------------------------------------------*/ /** * @fn [CS_API] int is_digital ( * const char* __restrict param_value, * long long* __restrict param_digit, * int* param_base ); * @brief Проверка, представляет ли значение строки число. * * Если значение строки представления @a param_value содержит число в строковом * виде, то функция возвращает число в формате long long. Значение * базовой системы исчисления при этом записывается в параметр @a param_base, * если его указатель не NULL * * @warning Числа с "плавающей запятой" определяются корректно, но результат не * содержит остатка (приводится к длинному целому). * * Используемы строковые представления чисел: * Формат десятичных чисел - <значение>[.e[+|-]<степень>]> * Формат шеснадцатиричных чисел - [<значение><[h|H]> | [0x|0X]<значение>] * Формат восьмиричных чисел - <значение><[o|O]> * Формат двоичных чисел - <значение><[b|B]> * * @param param_value - строка представления * @param param_digit - число после преобразования строкового значения * @param param_base - базовая система исчисления для числового значения * @return Представлено тремя значениями: * 0, значение строки представления @a param_value - строка. * 1, значение строки представления @a param_value - число. * -1, возникла системная ошибка. * При значении кода результата, равного -1, значение системной переменной errno * уставнавливается в одно из вариантов: * EINVAL - ошибка в значениях параметров функции; * ERANGE - ошибка размера числа в количестве цифр * ENOMEM - ошибка распределения памяти при вычислении * EBADE - ошибка копирования областей памяти при вычислении * */ CS_API(int) is_digital ( const char* __restrict param_value, long long* __restrict param_digit, int* param_base ); /*----------------------------------------------------------------------------*/ /** * @fn CS_API(char*) ltrim( const char* param_str ); * @brief Обрезка всех левых пробелов строки. * * @param param_str - исходная строка * @return Приведённая строка или NULL. * * Возвращаемое значение - указатель на первый символ @a param_str, не равный * знаку "пробел". * При значении кода результата, равного NULL, значение системной переменной errno * уставнавливается в EINVAL (ошибка в значениях параметров функции); */ CS_API(char*) ltrim( const char* param_str ); /*----------------------------------------------------------------------------*/ /** * @fn CS_API(char*) rtrim( const char* param_str ); * @brief Обрезка всех правых пробелов строки. * * @param param_str - исходная строка * @return Приведённая (static pointer) строка или NULL. * При значении кода результата, равного NULL, значение системной переменной errno * уставнавливается в одно из вариантов: * EINVAL - ошибка в значениях параметров функции; * ENOMEM - ошибка распределения памяти при вычислении * EBADE - ошибка копирования областей памяти при вычислении * * @warning Тип памяти результата - static pointer. Будте внимательны! */ CS_API(char*) rtrim( const char* param_str ); #ifdef __cplusplus } #endif #endif /* SC_COMMON_H */
Как старый склеротик с диагнозом «создал — оттестировал — работает — забыл» всегда пишу комментарии. Думаю, с описаниями функций всё ясно.
И основное объявление данных и методов конкретно для конфигурационного файла пропишем в
cs_configfile.h
#ifndef CONFIG_UTILS_H #define CONFIG_UTILS_H #include "../include/cs_types.h" /** * @typedef cfg_val_type_t * @brief Типы значений параметра */ typedef enum config_value_type { ptDigital = 0x01, /**< Значение параметра - число */ ptString = 0x02 /**< Значение параметра - строка символов */ } cfg_val_type_t; /** * @typedef config_t * @brief Тип экземпляра параметра конфигурации */ typedef struct { char* name; /**< Название параметра */ cfg_val_type_t type; /**< Тип параметьра @a param_type*/ union { /**< Значение параметра */ char* str; /**< Строковое представление */ struct { long long digit; /**< Числовое представление */ int base; /**< Базовая система числового представления */ }; }; } config_t; /** * @typedef txt_cfg_t * @brief Список параметров конфигурации. */ typedef struct txt_cfg { config_t value; struct txt_cfg* prev;/**< Указатель на предыдущий элемент списка */ struct txt_cfg* next;/**< Указатель на следующий элемент в списке */ } txt_cfg_t; #ifdef __cplusplus extern "C" { #endif /*----------------------------------------------------------------------------*/ /** * @fn [CS_API] txt_cfg_t* config_Open (const char* __restrict param_filename); * @brief Открывает файл параметров и формирует из него список типа @a txt_cfg_t. * * @param param_file_name - конфигурационный файл * @return Указатель на структуру списка параметров, сформированную чтением * названий и значений из файла \a param_filename или пустой указатель (NULL). * * Если функция возвращает NULL, то системная переменная errno содержит код * ошибки выполнения. * Возможные значения системной переменной errno: * EINVAL - пустой указатель в параметре param_filename * ENOMEM - ошибка распределения памяти * ERANGE - ошибка мат. вычислений * EBADE - ошибка при копировании строк * * Файл конфигурационных параметров состоит из коментариев, пустых строк и * кортежа параметров формата <название параметра> = <значение параметра>. * Например: * * # server_broadcast = ANY (по умолчанию) * server_broadcast = ANY * * # Порт для подключения к серверу * server_port = 8989 * * # Семейство протоколов * # Возможные значения: * # * # server_family = SOCKET_STREAM (по умолчанию) * server_family = SOCKET_STREAM * * #usb_vendor = 0x1111 * usb_vendor = 0xffff * usb_product = 0xaaaa * usb_ep_in = 0x01 * * @see config_Close */ CS_API(txt_cfg_t*) config_Open (const char* __restrict param_filename); /*----------------------------------------------------------------------------*/ /** * @fn [CS_API] void config_Close (txt_cfg_t** param_item); * @brief Закрыть список параметров. * * @param param_item - список параметров типа @a txt_cfg_t * * После закрытия область памяти по значению указателя @a param_item освобождается. * Значение указателя устанавливается в NULL. * * @see @a config_Open * @see @a free */ CS_API(void) config_Close (txt_cfg_t** param_item); #ifdef __cplusplus } #endif #endif /* CONFIG_UTILS_H */
Видим, что есть лишь две функции для открытия и инициализации структуры параметров и закрытия с очисткой памяти.
Подумайте.
Возможно, ваши вкусы позволят добавить к перечислению
cfg_val_type_t, например, ptBool, создав ветку обработки параметра. Тогда вы — ленивее, чем я сам!Реализации.
Для общих утилит.
cs_common.c
#include "../include/cs_common.h" #include <stdlib.h> #include <errno.h> #include <limits.h> #include <string.h> #include <stdio.h> //#include <netdb.h> /*----------------------------------------------------------------------------*/ int is_digital ( const char* __restrict param_value, long long* __restrict param_digit, int* param_base ) { char* c_endptr; char* c_value; int i_base; int i_ret = 1; size_t u_len; errno = 0; if(param_value == NULL || param_digit == NULL) { errno = EINVAL; return (-1); } *param_digit = 0; errno = 0; /* определение системы исчисления */ switch(param_value[strlen(param_value)-1]) { case 'h' : case 'H' : i_base = 16; break; case 'o' : case 'O' : i_base = 8; break; case 'b' : case 'B' : i_base = 2; break; default : i_base = 10; } if(i_base == 10) { if(param_value[strlen(param_value)-1] < (int)'0' || param_value[strlen(param_value)-1] > (int)'9') { return (0); /*строка*/ } } u_len = strlen(param_value); if(u_len > 1) { if((c_value = malloc(u_len+1)) == NULL) { /* Да, на вас ОЗУ не напасёшься! */ errno = ENOMEM; return (-1); } if((c_value = strncpy(c_value, param_value, u_len)) == NULL) { errno = EBADE; i_ret = -1; } else { /* проверка возможных вариантов строкового представления 16-х чисел */ if(param_value[0] == '0') { switch(param_value[1]) { case 'X' : case 'x' : i_base = 16; break; default : ; } if(i_base == 16) { if((c_value = strncpy(c_value, param_value+2, u_len-2)) == NULL) { errno = EBADE; i_ret = -1; } else { if( strncpy(c_value+u_len-2, "h\0", 2) == NULL ) { errno = EBADE; i_ret = -1; } } } } } if(i_ret > -1) { /* строку - в целое */ *param_digit = strtoll(c_value, &c_endptr, i_base); if(c_endptr == c_value) { /* строка */ i_ret = 0; } else { /* целое число */ if( (errno == ERANGE && (*param_digit == LLONG_MIN || *param_digit == LLONG_MAX)) || (errno != 0 && *param_digit == 0) ) { /* ошибка преобразования */ i_ret = -1; } } } /* если требуется вернуть в параметре систему исчисления */ if(param_base != NULL) *param_base = i_base; /* освободим память */ free(c_value); } return(i_ret); } /*----------------------------------------------------------------------------*/ char* ltrim ( const char* param_str ) { size_t i; size_t u_length; if(param_str == NULL) { errno = EINVAL; return (NULL); } errno = 0; u_length = strlen(param_str); if(0 == u_length) return (""); for(i = 0; i < u_length; i++ ) if((int)(param_str[i]) != ' '/* пробел */) /* перенос указателя на первый символ, отличный от пробела*/ return ((char*)param_str+i); return (NULL); } /*----------------------------------------------------------------------------*/ char* rtrim( const char* param_str ) { size_t i; size_t u_length; char* ptr; /* !!! */ static /* !!! */ char* str; if(NULL == param_str) { errno = EINVAL; return (NULL); } u_length = strlen(param_str); if(u_length == 0) return (""); for(i = u_length, errno = 0; (0 == errno) && (i > 0); i--) { if((int)(param_str[i-1]) == ' ') continue; if(NULL != (ptr = malloc(i + 1))) { if(NULL != (str = strncpy(ptr, param_str, i))) { str[i] = '\0'; return(str); } else { free(ptr); errno = EBADE; } } else { errno = ENOMEM; } } return (NULL); }
Для работы с файлом настроек.
cs_configfile.c
#include "../include/cs_configfile.h" #include "../include/cs_common.h" #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <limits.h> #include <string.h> /*----------------------------------------------------------------------------*/ /**@internal * @fn void free_cfg_data (txt_cfg_t* param_result); * @brief Освобождение пула пямяти структуры параметра типа @a txt_cfg_t. * * @param param_result - указатель на выделенный и заполненный значениями пул. */ void free_cfg_data (txt_cfg_t* param_result) { txt_cfg_t* p; errno = 0; for(p = param_result; p != NULL; p = p->next) { free(p->value.name); if(p->value.type == ptString) free(p->value.str); } if(param_result) free(param_result); } /*----------------------------------------------------------------------------*/ /**@internal * @fn int alloc_cfg_data ( * const char* __restrict param_name, * const char* __restrict param_value, * txt_cfg_t** __restrict param_result * ); * @brief Распределение памяти для param_result типа txt_cfg_t. * * @param param_name - название параметра * @param param_value - строковое представление значение * @param param_result - указатель на выделенный и заполненный значениями пул * структуры параметра типа txt_params_t. * * @return Значение 0 при успешном выполнении и -1 при ошике распределения. * При получении кода возврата, равного -1, следует рассмотреть системную * переменную errno на предмет кода ошибки. * * @warning Если передаётся непустой указатель *param_result на распределённый * пул памяти, то он будет сброшен без проверки при перераспределении памяти. * Следует позаботится о хранении указателя до вызова функции или сбросить его * самостятельно. */ int alloc_cfg_data ( const char* __restrict param_name, const char* __restrict param_value, txt_cfg_t** __restrict param_result ) { size_t u_length_n, u_length_v; if(param_name == NULL || param_value == NULL || param_result == NULL) { errno = EINVAL; return(-1); } errno = 0; if((*param_result = malloc(sizeof(txt_cfg_t))) == NULL) return(-1); /* Обязательное "обнуление"! * При возможном переходе на метку освобождения общей памяти (вызов * free_param(...)) после неудачного распределения памяти для названия * параметра, произойдёт проверка поля str структуры на NULL. Сравнение * указателя не даст значения NULL, т.к. не устанавливается по умолчанию. * Следствие - разрушение системы вызовом стандартной free() */ if(memset (*param_result, 0, sizeof(txt_cfg_t)) == NULL) return(-1); u_length_n = strlen(param_name); if(u_length_n == 0) return(-1); (*param_result)->value.name = malloc(u_length_n+1); if((*param_result)->value.name == NULL) /* ошибка распределения памяти */ goto free; if(strncpy((*param_result)->value.name, param_name, u_length_n) != NULL) { /* успешное копирование строки */ switch( is_digital( param_value, &((*param_result)->value.digit), &((*param_result)->value.base) ) ) { case 0 : { /* строка */ u_length_v = strlen(param_value); if(u_length_v > 0) { /* непустиая строка */ (*param_result)->value.str = malloc(u_length_v+1); if((*param_result)->value.str == NULL) goto free; if(strncpy((*param_result)->value.str, param_value, u_length_v) == NULL) goto free; } (*param_result)->value.type = ptString; } break; case 1 : /* число */ (*param_result)->value.type = ptDigital; break; default : { /* что-то невозможное */ errno = ERANGE; goto free; } } return (0); } free: free_cfg_data(*param_result); *param_result = NULL; return (-1); } /*----------------------------------------------------------------------------*/ /**@internal * * Разбор строки типа <параметр> = <значение> на две строки с названием параметра * и строковым представлением значения. * * @param param_str - строка типа [параметр] = [значение] * @param param_name - указатель на название параметра * @param param_value - указатель на значение параметра * @return Возвращает 0 при успешном выполнении и -1 при ошике распределения. * * При получении кода возврата, равного -1, следует рассмотреть системную * переменную errno на предмет кода ошибки. * * @warning Если передаваемые указатели *param_value и/или *param_name не NULL, * они будут сброшены без очистки памяти. Ответственность за распределение и * очистку пямяти лежит на разработчике. */ int parse_string ( const char* param_str, char** param_name, char** param_value ) { if(param_name == NULL || param_value == NULL) { errno = EINVAL; return -1; } *param_value = *param_name = NULL; if(param_str == NULL) { errno = EINVAL; return -1; } switch(param_str[0]) { case '\n' : case '#' : case 0 : return (0); } char c_name[MAX_INPUT]; char c_value[MAX_INPUT]; errno = 0; int i = sscanf(param_str, "%s = %s", c_name, c_value); char* p_name = ltrim(rtrim(c_name)); char* p_value = ltrim(rtrim(c_value)); if(errno != 0 || i != 2 ) return (-1); *param_name = malloc(strlen(p_name)+1); if(*param_name != NULL) { *param_value = malloc(strlen(p_value)+1); if(*param_value != NULL) { if(strcpy(*param_name, p_name) == NULL) { /* ошибка копирования */ errno = EXFULL; goto freemem; } if(strcpy(*param_value, p_value) == NULL) { /* ошибка копирования */ errno = EXFULL; goto freemem; } #ifdef DEBUG_MODE if(debug_level > 3) { printf("DEBUG parse_string(...). c_name = %s, c_value = %s, ", c_name, c_value); printf("*param_name = %s, *param_value = %s\n", *param_name, *param_value); } #endif return (0); } } freemem: if(*param_name != NULL) free(*param_name); if(*param_value != NULL) free(*param_value); *param_value = *param_name = NULL; return(-1); } /*----------------------------------------------------------------------------*/ /**@internal * * Разбор файла конфигурационных параметров. * * @param param_file_name - название файла конфигурации с полным описанием пути * к каталогу * @param param_result - указатель на список структур описания параметров * @return При успешном завершении возвращает 0 и заполненный список параметров * и их значений @a param_result. Иначе возвращает -1. * * @warning Если передаваемый указатель *param_result не NULL, он будет сброшен * без очистки памяти. Ответственность за распределение и очистку пямяти лежит * на разработчике. * * Функция последовательно считывает строки, пропуская незначащие и заполняет * список параметров типа @a txt_params_t. * При ошибочном выполнении (код возврата -1) разработчик может прочесть код * ошибки из системной переменной errno. При этом значение указателя *param_result * сбрасывается в NULL (память для него не распределяется) * */ int parse_config_file ( const char* param_file_name, txt_cfg_t** param_result ) { if(param_result == NULL || param_result == NULL) { errno = EINVAL; return (-1); } char c_str[MAX_INPUT]; txt_cfg_t* t_param; char* c_name; char* c_value; errno = 0; FILE* p_fd = fopen(param_file_name, "r"); if(p_fd == NULL) /* невозможно получить указатель на структуру файла */ return (-1); int i_res = 0; *param_result = NULL; errno = 0; /* чтение строк конфигурации до конца файла */ while(fgets(c_str,MAX_INPUT,p_fd) != NULL && i_res == 0 ) { /* разбор очередной строки */ i_res = parse_string(c_str, &c_name, &c_value); if(i_res == 0) { if(c_name == NULL) /* получена незначащая строка (коментарий, перевод строки,...) */ continue; #ifdef DEBUG_MODE if(debug_level > 3) printf( "DEBUG parse_confif_file(...): source = \"%s\", name = %s, value = %s \n", c_str, c_name, c_value ); #endif if((i_res = alloc_cfg_data(c_name, c_value, &t_param)) == 0 ) { /* ресурсы распределены */ t_param->next = *param_result; if(*param_result != NULL) (*param_result)->prev = t_param; } /* иначе выход из цикла по условию значения i_res */ /* Обратить внимание: обязательная очистка памяти! */ free(c_name); free(c_value); *param_result = t_param; }/* иначе выход из цикла по условию значения i_res */ } if(i_res != 0) { free_cfg_data(*param_result); *param_result = NULL; } fclose(p_fd); return (i_res); } /*----------------------------------------------------------------------------*/ txt_cfg_t* config_Open (const char* param_filename) { txt_cfg_t* p_item = NULL; (void)parse_config_file(param_filename, &p_item); return (p_item); } /*----------------------------------------------------------------------------*/ void config_Close (txt_cfg_t** param_item) { if(param_item) { if(*param_item) { free_cfg_data(*param_item); *param_item = NULL; } } }
Подумайте!
В теле функции
parse_string попробуйте применить правило разбора регулярных выражений. Так вы избавитесь от обязательства ставить пробелы по обе стороны знака «равно».Мне было лень.
Вот, и всё. :)
Тестируем.
Я тспользую для работы на С IDE NetBeans. Почему? Нравится! :)
В данной среде для построения тестов применяю Framework CUnit.
Утилита ltrim
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Test: test1…
Проверка.
Строка " test1". Результат: «test1»
Строка " test 2". Результат: «test 2»
Строка " !23test3". Результат: "!23test3"
passed
suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a
Elapsed time = 0.000 seconds
Утилита rtrim
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Test: test1…
Проверка.
Строка «test1». Результат: «test1»
Строка «test 2 ». Результат: «test 2»
Строка "!23test3# ". Результат: "!23test3#"
passed
suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a
Elapsed time = 0.000 seconds
Утилита is_digital
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 0xffff
Ваша строка: «0xffff»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 65535, Система исчисления = 16
passed
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a
Elapsed time = 0.000 seconds
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 1277o
Ваша строка: «1277o»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 703, Система исчисления = 8
passed
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a
Elapsed time = 0.000 seconds
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 4ret
Ваша строка: «4ret»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 0, Система исчисления = 0
FAILED
1. tests/is_digital_test.c:59 — (i_result = is_digital(c_test, &ll_digit, &i_base)) == 1
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 1 1 0
asserts 2 2 1 1 n/a
Elapsed time = 0.000 seconds
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Введите строку для проверки на представление числового значения: 1024
Ваша строка: «1024»
Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 1024, Система исчисления = 10
passed
Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a
Elapsed time = 0.000 seconds
Разбор файла конфигурации.
Файл:
#------------------------------------------------------------------------------- # Правило: Знак равенства разделён хотя бы одним пробелом с названием # параметра и значением параметра. #------------------------------------------------------------------------------- # #pid_file = ereusb.pid # Тип вещания сервера # Возможные значения: bla, bla, bla... # # server_broadcast = ANY (по умолчанию) server_broadcast = ANY # Порт для подключения к серверу server_port = 8989 # Семейство протоколов # Возможные значения: # # server_family = SOCKET_STREAM (по умолчанию) server_family = SOCKET_STREAM #usb_vendor = 0x1111 usb_vendor = 0001b usb_product = 1234o usb_ep_in = 0x01 usb_ep_out = 0x82
Результат:
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net
Test: test_parse_confif_file…
Параметр:
Название — «usb_ep_out»
Тип — число
Значение — 0x0082
Базовая система — 16
Параметр:
Название — «usb_ep_in»
Тип — число
Значение — 0x0001
Базовая система — 16
Параметр:
Название — «usb_product»
Тип — число
Значение — 1234
Базовая система — 8
Параметр:
Название — «usb_vendor»
Тип — число
Значение — 1
Базовая система — 2
Параметр:
Название — «server_family»
Тип — строка
Значение — «SOCKET_STREAM»
Параметр:
Название — «server_port»
Тип — число
Значение — 8989
Базовая система — 10
Параметр:
Название — «server_broadcast»
Тип — строка
Значение — «ANY»
passed
suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a
Elapsed time = 0.000 seconds
cunit.sourceforge.net
Test: test_parse_confif_file…
Параметр:
Название — «usb_ep_out»
Тип — число
Значение — 0x0082
Базовая система — 16
Параметр:
Название — «usb_ep_in»
Тип — число
Значение — 0x0001
Базовая система — 16
Параметр:
Название — «usb_product»
Тип — число
Значение — 1234
Базовая система — 8
Параметр:
Название — «usb_vendor»
Тип — число
Значение — 1
Базовая система — 2
Параметр:
Название — «server_family»
Тип — строка
Значение — «SOCKET_STREAM»
Параметр:
Название — «server_port»
Тип — число
Значение — 8989
Базовая система — 10
Параметр:
Название — «server_broadcast»
Тип — строка
Значение — «ANY»
passed
suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a
Elapsed time = 0.000 seconds
Благодарности.
- Деннису Ритчи(R.I.P.) за гениальную разработку языка программирования «C»;
- Sun Microsystems (R.I.P.) за разработку IDE NetBeans;
- Джону Дугласу Лорду (R.I.P.) за «Concerto For Group and Orchestra», сопровождавшего пост.
Всех благ!
