441 lines
11 KiB
C
441 lines
11 KiB
C
#include <stdlib.h>
|
|
#include "pine-ini.h"
|
|
|
|
#define isWhitespace(c) ((c) == ' ' || c == '\r' || c == '\n' || c == '\t')
|
|
|
|
const char * PINE_INI_ERRMSG[] = {
|
|
"No error",
|
|
"Illegal pattern",
|
|
"Empty key",
|
|
"Sections exceed",
|
|
"Parameters exceed"
|
|
};
|
|
|
|
//
|
|
// * Parser
|
|
//
|
|
|
|
#define PARSER_ACTION_CONTINUE 1
|
|
#define PARSER_ACTION_END 0
|
|
|
|
typedef struct tagPineIniParser {
|
|
const char * textBuffer;
|
|
const char * currentPointer;
|
|
int lineNumber;
|
|
} PineIniParser;
|
|
|
|
static PineIniParser* PineIni_Parser_New(const char* textBuffer) {
|
|
PineIniParser* parser = (PineIniParser *)malloc(sizeof(PineIniParser));
|
|
|
|
parser->textBuffer = textBuffer;
|
|
parser->currentPointer = parser->textBuffer;
|
|
parser->lineNumber = 0;
|
|
|
|
return parser;
|
|
}
|
|
|
|
static void PineIni_Parser_Destory(PineIniParser* parser) {
|
|
if (parser) {
|
|
free(parser);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return
|
|
// PARSER_ACTION_END -> buffer end
|
|
// PARSER_ACTION_CONTINUE -> buffer has follow-up content
|
|
//
|
|
static int PineIni_Parser_Gets(PineIniParser* parser, char* buf) {
|
|
char *pBuf = buf;
|
|
|
|
parser->lineNumber++;
|
|
|
|
while (*parser->currentPointer != '\n' && *parser->currentPointer != '\0') {
|
|
*(pBuf++) = *(parser->currentPointer++);
|
|
}
|
|
|
|
*pBuf = '\0';
|
|
|
|
if (*parser->currentPointer == '\0') {
|
|
return PARSER_ACTION_END;
|
|
}
|
|
else {
|
|
// Skip LF
|
|
parser->currentPointer++;
|
|
return PARSER_ACTION_CONTINUE;
|
|
}
|
|
}
|
|
|
|
|
|
static PINE_BOOL PineIni_Line_Empty(const char* line, const int length) {
|
|
if (length <= 0 || PineIni_StringEquals("", line)) {
|
|
return PINE_TRUE;
|
|
}
|
|
return PINE_FALSE;
|
|
}
|
|
|
|
|
|
static PINE_BOOL PineIni_Line_IsComment(const char* line, const int length) {
|
|
if (length >= 1 && line[0] == ';') {
|
|
return PINE_TRUE;
|
|
}
|
|
return PINE_FALSE;
|
|
}
|
|
|
|
static PINE_BOOL PineIni_Line_IsSection(const char* line, const int length) {
|
|
if (length >= 2 && line[0] == '[' && line[length - 1] == ']') {
|
|
return PINE_TRUE;
|
|
}
|
|
return PINE_FALSE;
|
|
}
|
|
|
|
static PINE_BOOL PineIni_Line_IsParameter(const char* line, const int length) {
|
|
int i;
|
|
for (i = 0; i < length; ++i) {
|
|
if (line[i] == '=') {
|
|
return PINE_TRUE;
|
|
}
|
|
}
|
|
return PINE_FALSE;
|
|
}
|
|
|
|
//
|
|
// * Parse API
|
|
//
|
|
|
|
#define PARSER_RETURN_ERROR(error_code) (void *) 0; \
|
|
errorRet->lineNumber = parser->lineNumber; \
|
|
errorRet->errorCode = (error_code); \
|
|
strcpy(errorRet->lineContent, lineBuffer); \
|
|
PineIni_Parser_Destory(parser); \
|
|
PineIni_Destory(iniResult); \
|
|
return NULL;
|
|
|
|
PineIniFile* PineIni_Parse(const char* iniText, PineIniError* errorRet) {
|
|
char lineBuffer[PINE_INI_LINE_MAX_LEN];
|
|
int lineLength;
|
|
int getsRetCode;
|
|
PineIniParser* parser;
|
|
PineIniFile* iniResult;
|
|
PineIniSection* currentSection;
|
|
|
|
parser = PineIni_Parser_New(iniText);
|
|
|
|
// Create iniResult
|
|
iniResult = (PineIniFile *)malloc(sizeof(PineIniFile));
|
|
iniResult->numSection = 0;
|
|
|
|
// Create a default section for file
|
|
// and append to iniResult
|
|
currentSection = PineIni_Section_New("_default");
|
|
PineIni_Append(iniResult, currentSection);
|
|
|
|
// Clear error info
|
|
errorRet->lineNumber = 0;
|
|
errorRet->errorCode = PINE_INI_ERRCODE_NO;
|
|
strcpy(errorRet->lineContent, "");
|
|
|
|
do {
|
|
// Get line from parser
|
|
getsRetCode = PineIni_Parser_Gets(parser, lineBuffer);
|
|
|
|
// Trim white spaces
|
|
PineIni_StringTrim(lineBuffer);
|
|
|
|
// Check pattern
|
|
lineLength = strlen(lineBuffer);
|
|
|
|
// Pattern : empty
|
|
if (PineIni_Line_Empty(lineBuffer, lineLength)) {
|
|
continue;
|
|
}
|
|
// Pattern : comment
|
|
else if (PineIni_Line_IsComment(lineBuffer, lineLength)) {
|
|
continue;
|
|
}
|
|
// Pattern : section
|
|
else if (PineIni_Line_IsSection(lineBuffer, lineLength)) {
|
|
char sectionName[PINE_INI_LINE_MAX_LEN];
|
|
PineIniSection* section;
|
|
|
|
PineIni_Substring(sectionName, lineBuffer, 1, lineLength - 2);
|
|
|
|
section = PineIni_Find(iniResult, sectionName);
|
|
|
|
// Section name not exist in iniResult
|
|
if (section == NULL) {
|
|
// Check if exceed
|
|
if (iniResult->numSection >= PINE_INI_MAX_NUM_SECTIONS) {
|
|
PARSER_RETURN_ERROR(PINE_INI_ERRCODE_SECTION_EXCEED);
|
|
}
|
|
|
|
// Create a new section and append in iniResult
|
|
section = PineIni_Section_New(sectionName);
|
|
PineIni_Append(iniResult, section);
|
|
}
|
|
|
|
currentSection = section;
|
|
|
|
continue;
|
|
}
|
|
// Pattern : parameter
|
|
else if (PineIni_Line_IsParameter(lineBuffer, lineLength)) {
|
|
char key[PINE_INI_LINE_MAX_LEN];
|
|
char value[PINE_INI_LINE_MAX_LEN];
|
|
int equalPos;
|
|
PineIniParameter* param;
|
|
|
|
// Get key and value
|
|
equalPos = PineIni_FindChar(lineBuffer, '=');
|
|
PineIni_Substring(key, lineBuffer, 0, equalPos - 1);
|
|
PineIni_Substring(value, lineBuffer, equalPos + 1, lineLength - 1);
|
|
PineIni_StringTrim(key);
|
|
PineIni_StringRemoveQuotes(value);
|
|
|
|
// Error: empty key
|
|
if (strlen(key) <= 0) {
|
|
PARSER_RETURN_ERROR(PINE_INI_ERRCODE_EMPTY_KEY);
|
|
}
|
|
|
|
// Check if key already exist
|
|
param = PineIni_Section_Find(currentSection, key);
|
|
|
|
// Exist, update value
|
|
if (param) {
|
|
PineIni_Parameter_Assign(param, value);
|
|
}
|
|
// Not exist, append to current section
|
|
else {
|
|
// Check if exceed
|
|
if (currentSection->numParam >= PINE_INI_MAX_NUM_PARAMETERS) {
|
|
PARSER_RETURN_ERROR(PINE_INI_ERRCODE_PARAMS_EXCEED);
|
|
}
|
|
PineIni_Section_Append(currentSection, key, value);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
// Illegal Pattern!
|
|
else {
|
|
PARSER_RETURN_ERROR(PINE_INI_ERRCODE_ILLEGAL_PATTERN);
|
|
}
|
|
|
|
} while(getsRetCode != PARSER_ACTION_END);
|
|
|
|
PineIni_Parser_Destory(parser);
|
|
|
|
return iniResult;
|
|
}
|
|
|
|
PineIniSection* PineIni_Find(PineIniFile* file, const char* sectionName) {
|
|
if (file) {
|
|
int i;
|
|
for (i = 0; i < file->numSection; ++i) {
|
|
PineIniSection* section = file->sections[i];
|
|
if (PineIni_StringEquals(sectionName, section->name)) {
|
|
return section;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PineIniFile* PineIni_Append(PineIniFile* file, PineIniSection* section) {
|
|
if (file) {
|
|
file->sections[file->numSection++] = section;
|
|
}
|
|
return file;
|
|
}
|
|
|
|
void PineIni_Destory(PineIniFile* file) {
|
|
if (file) {
|
|
int i;
|
|
|
|
// destory sections in file
|
|
for (i = 0; i < file->numSection; ++i) {
|
|
PineIni_Section_Destory(file->sections[i]);
|
|
}
|
|
|
|
// free file self
|
|
free(file);
|
|
}
|
|
}
|
|
|
|
// Section
|
|
|
|
PineIniSection* PineIni_Section_New(const char* sectionName) {
|
|
PineIniSection* section = (PineIniSection *)malloc(sizeof(PineIniSection));
|
|
|
|
section->name = PineIni_StringDump(sectionName);
|
|
section->numParam = 0;
|
|
|
|
return section;
|
|
}
|
|
|
|
PineIniParameter* PineIni_Section_Find(PineIniSection* section, const char* key) {
|
|
if (section) {
|
|
int i;
|
|
for (i = 0; i < section->numParam; ++i) {
|
|
PineIniParameter* param = section->params[i];
|
|
if (PineIni_StringEquals(key, param->key)) {
|
|
return param;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PineIniSection* PineIni_Section_Append(PineIniSection* section, const char* key, const char* value) {
|
|
if (section) {
|
|
section->params[section->numParam++] = PineIni_Parameter_New(key, value);
|
|
}
|
|
return section;
|
|
}
|
|
|
|
void PineIni_Section_Destory(PineIniSection* section) {
|
|
if (section) {
|
|
int i;
|
|
|
|
// free section name
|
|
if (section->name) {
|
|
free(section->name);
|
|
}
|
|
// destory parameters in section
|
|
for (i = 0; i < section->numParam; ++i) {
|
|
PineIni_Parameter_Destory(section->params[i]);
|
|
}
|
|
// free section self
|
|
free(section);
|
|
}
|
|
}
|
|
|
|
// Parameter
|
|
|
|
PineIniParameter* PineIni_Parameter_New(const char* key, const char* value) {
|
|
PineIniParameter* param;
|
|
|
|
param = (PineIniParameter *)malloc(sizeof(PineIniParameter));
|
|
param->key = PineIni_StringDump(key);
|
|
param->value = PineIni_StringDump(value);
|
|
|
|
return param;
|
|
}
|
|
|
|
PineIniParameter* PineIni_Parameter_Assign(PineIniParameter* param, const char* value) {
|
|
if (param) {
|
|
if (param->value) {
|
|
free(param->value);
|
|
}
|
|
param->value = PineIni_StringDump(value);
|
|
}
|
|
return param;
|
|
}
|
|
|
|
void PineIni_Parameter_Destory(PineIniParameter* param) {
|
|
if (param) {
|
|
if (param->key) {
|
|
free(param->key);
|
|
}
|
|
if (param->value) {
|
|
free(param->value);
|
|
}
|
|
free(param);
|
|
}
|
|
}
|
|
|
|
// Utils functions
|
|
|
|
int PineIni_FindChar(const char* src, const char find) {
|
|
int i;
|
|
for (i = 0; src[i]; ++i) {
|
|
if (src[i] == find) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
char* PineIni_Substring(char* dest, const char* src, int left, int right) {
|
|
char* buf = dest;
|
|
int newLength = 0;
|
|
int i;
|
|
|
|
for (i = left; i <= right; ++i) {
|
|
buf[newLength++] = src[i];
|
|
}
|
|
|
|
buf[newLength] = 0;
|
|
|
|
return dest;
|
|
}
|
|
|
|
char* PineIni_ToSubstring (char* src, int left, int right) {
|
|
int length = strlen(src);
|
|
char* buf = (char *)malloc(length + 1);
|
|
int newLength = 0;
|
|
int i;
|
|
|
|
for (i = left; i <= right; ++i) {
|
|
buf[newLength++] = src[i];
|
|
}
|
|
|
|
buf[newLength] = 0;
|
|
|
|
strcpy(src, buf);
|
|
|
|
free(buf);
|
|
|
|
return src;
|
|
}
|
|
|
|
char* PineIni_StringDump (const char* src) {
|
|
int length = strlen(src);
|
|
char* dup = (char *)malloc(length + 1);
|
|
strcpy(dup, src);
|
|
return dup;
|
|
}
|
|
|
|
|
|
char* PineIni_StringTrim(char* src) {
|
|
int length = strlen(src);
|
|
int left;
|
|
int right;
|
|
int newLength = 0;
|
|
|
|
for (left = 0; left < length; ++left) {
|
|
if (!isWhitespace(src[left])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (right = length - 1; right >= 0; --right) {
|
|
if (!isWhitespace(src[right])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return PineIni_ToSubstring(src, left, right);
|
|
}
|
|
|
|
char* PineIni_StringRemoveQuotes(char* src) {
|
|
int length;
|
|
int isSingleQuotesAround;
|
|
int isDoubleQuotesAround;
|
|
|
|
|
|
PineIni_StringTrim(src);
|
|
|
|
// Too short, no need to check
|
|
if ((length = strlen(src)) < 2) {
|
|
return src;
|
|
}
|
|
|
|
isSingleQuotesAround = (src[0] == '"' && src[length - 1] == '"');
|
|
isDoubleQuotesAround = (src[0] == '\'' && src[length - 1] == '\'');
|
|
|
|
if (!isSingleQuotesAround && !isDoubleQuotesAround) {
|
|
return src;
|
|
}
|
|
|
|
return PineIni_ToSubstring(src, 1, length - 2);
|
|
} |