569 lines
14 KiB
C++
569 lines
14 KiB
C++
// +-------------------------------------------------------------------------+
|
|
// | I n i F i l e vers. 0.3.30 |
|
|
// | Copyright (c) Andrey Vikt. Stolyarov [http://www.croco.net/] 2003-2024 |
|
|
// | ----------------------------------------------------------------------- |
|
|
// | This is free software. Permission is granted to everyone to use, copy |
|
|
// | or modify this software under the terms and conditions of |
|
|
// | GNU LESSER GENERAL PUBLIC LICENSE, v. 2.1 |
|
|
// | as published by Free Software Foundation (see the file LGPL.txt) |
|
|
// | |
|
|
// | Please visit http://www.croco.net/software/scriptpp to get a fresh copy |
|
|
// | ----------------------------------------------------------------------- |
|
|
// | This code is provided strictly and exclusively on the "AS IS" basis. |
|
|
// | !!! THERE IS NO WARRANTY OF ANY KIND, NEITHER EXPRESSED NOR IMPLIED !!! |
|
|
// +-------------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "inifile.hpp"
|
|
|
|
IniFileParser::IniFileParser()
|
|
{
|
|
firstgroup = 0;
|
|
last_error_line = -1;
|
|
last_error_description = 0;
|
|
}
|
|
|
|
IniFileParser::~IniFileParser()
|
|
{
|
|
if(firstgroup)
|
|
delete firstgroup;
|
|
}
|
|
|
|
static bool is_space(int c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
|
}
|
|
|
|
static bool is_empty_line(const char *s)
|
|
{
|
|
int idx = 0;
|
|
do {
|
|
if(s[idx] == ';' || s[idx] == '#') /* comment */
|
|
return true;
|
|
if(!is_space(s[idx]))
|
|
return false;
|
|
idx++;
|
|
} while(s[idx]);
|
|
return true;
|
|
}
|
|
|
|
bool IniFileParser::Load(const char *path)
|
|
{
|
|
last_error_line = -1;
|
|
int current_line = 0;
|
|
last_error_description = 0;
|
|
FILE *fl = fopen(path, "r");
|
|
if(!fl) {
|
|
last_error_description = "couldn't open the file";
|
|
return false;
|
|
}
|
|
|
|
bool result = false; // so ``goto quit'' causes returning false
|
|
|
|
char buf[1024];
|
|
buf[sizeof(buf)-1] = '\0';
|
|
Section *current_section = 0;
|
|
Parameter *current_parameter = 0;
|
|
while(fgets(buf, sizeof(buf), fl)) {
|
|
current_line++;
|
|
if(buf[sizeof(buf)-1]!='\0') {
|
|
last_error_line = current_line;
|
|
last_error_description = "line too long (max 1024)";
|
|
goto quit;
|
|
}
|
|
if(!buf[0] || is_empty_line(buf)) {
|
|
/* empty line */
|
|
continue;
|
|
} else if(is_space(buf[0]) || buf[0] == '+') {
|
|
/* continued line */
|
|
if(!current_parameter) {
|
|
last_error_line = current_line;
|
|
last_error_description = "data outside of a parameter";
|
|
goto quit;
|
|
}
|
|
current_parameter->AddDictionaryLine(buf+1);
|
|
} else if(buf[0] == '#' || buf[0] == ';') {
|
|
/* comment -- ignore */
|
|
continue;
|
|
} else if(buf[0] == '[') {
|
|
/* section header */
|
|
char* groupname;
|
|
char* sectionname;
|
|
if(!ExtractHeaderData(buf, &groupname, §ionname)) {
|
|
last_error_line = current_line;
|
|
last_error_description = "invalid header syntax";
|
|
goto quit;
|
|
}
|
|
current_section = ProvideSection(groupname, sectionname);
|
|
} else {
|
|
/* must be a dictionary line */
|
|
if(!current_section) {
|
|
last_error_line = current_line;
|
|
last_error_description = "data outside of a section";
|
|
goto quit;
|
|
}
|
|
char * name;
|
|
char * value;
|
|
if(!ExtractDictionaryData(buf, &name, &value)) {
|
|
last_error_line = current_line;
|
|
last_error_description = "invalid dictionary line syntax";
|
|
goto quit;
|
|
}
|
|
current_parameter = current_section->ProvideParameter(name);
|
|
current_parameter->AddDictionaryData(value);
|
|
}
|
|
}
|
|
result = true;
|
|
quit:
|
|
fclose(fl);
|
|
return result;
|
|
}
|
|
|
|
int IniFileParser::GetGroupCount() const
|
|
{
|
|
int c;
|
|
Group *tmp;
|
|
for(tmp=firstgroup, c=0; tmp; tmp=tmp->next, c++)
|
|
;
|
|
return c;
|
|
}
|
|
|
|
int IniFileParser::GetSectionCount(const char *groupname) const
|
|
{
|
|
Group* pgrp = *FindGroupP(groupname);
|
|
if(!pgrp)
|
|
return 0;
|
|
int c;
|
|
Section *tmp;
|
|
for(tmp=pgrp->firstsection, c=0; tmp; tmp=tmp->next, c++)
|
|
;
|
|
return c;
|
|
}
|
|
|
|
const char* IniFileParser::GetSectionName(const char *grpnm, int sectidx) const
|
|
{
|
|
Group *grp = *FindGroupP(grpnm);
|
|
if(!grp)
|
|
return "";
|
|
int i;
|
|
Section *tmp;
|
|
for(tmp=grp->firstsection, i=0; tmp && i<sectidx; tmp=tmp->next, i++)
|
|
;
|
|
return tmp ? tmp->name : "";
|
|
}
|
|
|
|
const char* IniFileParser::GetParam(const char *groupname,
|
|
const char *sectionname,
|
|
const char *paramname) const
|
|
{
|
|
Group* grp = *FindGroupP(groupname);
|
|
if(!grp)
|
|
return 0;
|
|
Section* sect = *FindSectionP(grp, sectionname);
|
|
if(!sect)
|
|
return 0;
|
|
Parameter *tmp;
|
|
for(tmp=sect->firstparam; tmp; tmp=tmp->next) {
|
|
if(0 == strcmp(tmp->name, paramname))
|
|
return tmp->value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void IniFileParser::DeleteSection(const char *grpnm, const char *sectnm)
|
|
{
|
|
Group** grp = (Group**)FindGroupP(grpnm);
|
|
if(!*grp)
|
|
return;
|
|
Section** sect = (Section**)FindSectionP(*grp, sectnm);
|
|
if(!*sect)
|
|
return;
|
|
Section *tmp = *sect;
|
|
*sect = (*sect)->next;
|
|
tmp->next = 0;
|
|
delete tmp;
|
|
// Now remove the group if it's empty
|
|
if(!((*grp)->firstsection)) {
|
|
Group *tmp = *grp;
|
|
*grp = (*grp)->next;
|
|
tmp->next = 0;
|
|
delete tmp;
|
|
}
|
|
}
|
|
|
|
bool IniFileParser::ExtractHeaderData(char *buf, char **name, char **param)
|
|
{
|
|
char *p = buf;
|
|
while(*p&&is_space(*p))
|
|
p++;
|
|
if(*p!='[')
|
|
return false;
|
|
p++;
|
|
while(*p&&is_space(*p))
|
|
p++;
|
|
*name = p;
|
|
while(*p&&!is_space(*p) && !(*p==']'))
|
|
p++;
|
|
if(*p==']') {
|
|
// no param found
|
|
*param = 0;
|
|
*p = 0;
|
|
return true;
|
|
}
|
|
*p = 0;
|
|
p++;
|
|
while(*p&&is_space(*p))
|
|
p++;
|
|
*param = p;
|
|
while(*p&&!is_space(*p) && !(*p==']'))
|
|
p++;
|
|
if(*p==']') {
|
|
*p = 0;
|
|
return true;
|
|
}
|
|
*p=0;
|
|
p++;
|
|
// now check for the closing ']'
|
|
while(*p&&is_space(*p))
|
|
p++;
|
|
return *p == ']';
|
|
}
|
|
|
|
bool IniFileParser::ExtractDictionaryData(char *buf, char** name, char** value)
|
|
{
|
|
char *p = buf;
|
|
while(*p && is_space(*p))
|
|
p++;
|
|
*name = p;
|
|
while(*p && !is_space(*p))
|
|
p++;
|
|
if(is_space(*p)) {
|
|
*p=0;
|
|
p++;
|
|
while(*p && is_space(*p))
|
|
p++;
|
|
}
|
|
if(*p != '=')
|
|
return false;
|
|
*p=0;
|
|
p++;
|
|
while(*p && is_space(*p))
|
|
p++;
|
|
*value = p;
|
|
while(*p)
|
|
p++;
|
|
p--;
|
|
while(*p && is_space(*p))
|
|
p--;
|
|
p++;
|
|
*p=0;
|
|
return true;
|
|
}
|
|
|
|
IniFileParser::Section*
|
|
IniFileParser::ProvideSection(const char* groupname, const char* sectionname)
|
|
{
|
|
Group** ppgrp = (Group**)FindGroupP(groupname);
|
|
if(!(*ppgrp))
|
|
*ppgrp = new Group(groupname);
|
|
Section** ppsect = (Section**)FindSectionP(*ppgrp, sectionname);
|
|
if(!(*ppsect))
|
|
*ppsect = new Section(sectionname ? sectionname : "");
|
|
return *ppsect;
|
|
}
|
|
|
|
IniFileParser::Group*const*
|
|
IniFileParser::FindGroupP(const char* groupname) const
|
|
{
|
|
Group* const * ppgrp = &firstgroup;
|
|
while(*ppgrp && strcmp((*ppgrp)->name, groupname) != 0)
|
|
ppgrp = &((*ppgrp)->next);
|
|
return ppgrp;
|
|
}
|
|
|
|
IniFileParser::Section*const*
|
|
IniFileParser::FindSectionP(Group* pgrp, const char* sectionname) const
|
|
{
|
|
Section* const* ppsect = &(pgrp->firstsection);
|
|
if(sectionname)
|
|
while(*ppsect && strcmp((*ppsect)->name, sectionname) != 0)
|
|
ppsect = &((*ppsect)->next);
|
|
return ppsect;
|
|
}
|
|
|
|
long IniFileParser::
|
|
GetIntegerParameter(const char *grname, const char *sectname,
|
|
const char *parmname, long def) const
|
|
{
|
|
const char* res = GetParam(grname, sectname, parmname);
|
|
if(!res || res[0] == '\0')
|
|
return def;
|
|
char *err;
|
|
long n = strtol(res, &err, 10);
|
|
if(*err != '\0')
|
|
return def;
|
|
return n;
|
|
}
|
|
|
|
const char* IniFileParser::
|
|
GetTextParameter(const char *grname, const char *sectname,
|
|
const char *parmname, const char* def) const
|
|
{
|
|
const char* res = GetParam(grname, sectname, parmname);
|
|
return res ? res : def;
|
|
}
|
|
|
|
const char* IniFileParser::
|
|
GetModifiedTextParameter(const char *grname, const char *sectname,
|
|
const char *parmname, const char *modifier,
|
|
const char *def) const
|
|
{
|
|
int len1 = strlen(parmname);
|
|
char *modname = new char[len1 + 1 + strlen(modifier) + 1];
|
|
strcpy(modname, parmname);
|
|
modname[len1]=':';
|
|
strcpy(modname + len1 + 1, modifier);
|
|
const char* res = GetParam(grname, sectname, modname);
|
|
delete[] modname;
|
|
if(!res)
|
|
res = GetParam(grname, sectname, parmname);
|
|
return res ? res : def;
|
|
}
|
|
|
|
long IniFileParser::
|
|
GetModifiedIntegerParameter(const char *grname, const char *sectname,
|
|
const char *parmname, const char *modifier, long def) const
|
|
{
|
|
const char *res =
|
|
GetModifiedTextParameter(grname, sectname, parmname, modifier, 0);
|
|
if(!res)
|
|
return def;
|
|
if(res[0] == '\0')
|
|
return def;
|
|
char *err;
|
|
long n = strtol(res, &err, 10);
|
|
if(*err != '\0')
|
|
return def;
|
|
return n;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
IniFileParser::Group::Group(const char *aname)
|
|
{
|
|
name = new char[strlen(aname)+1];
|
|
strcpy(name, aname);
|
|
next = 0;
|
|
firstsection = 0;
|
|
}
|
|
|
|
IniFileParser::Group::~Group()
|
|
{
|
|
delete[] name;
|
|
if(next)
|
|
delete next;
|
|
if(firstsection)
|
|
delete firstsection;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
IniFileParser::Section::Section(const char *aname)
|
|
{
|
|
name = new char[strlen(aname) + 1];
|
|
strcpy(name, aname);
|
|
next = 0;
|
|
firstparam = 0;
|
|
}
|
|
|
|
IniFileParser::Section::~Section()
|
|
{
|
|
delete[] name;
|
|
if(next)
|
|
delete next;
|
|
if(firstparam)
|
|
delete firstparam;
|
|
}
|
|
|
|
IniFileParser::Parameter*
|
|
IniFileParser::Section::ProvideParameter(const char * name)
|
|
{
|
|
Parameter **tmp;
|
|
for(tmp = &firstparam; *tmp; tmp = &((*tmp)->next))
|
|
if(0 == strcmp((*tmp)->name, name))
|
|
return *tmp;
|
|
(*tmp) = new Parameter(name, "");
|
|
return *tmp;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
|
|
IniFileParser::Parameter::Parameter(const char *aname, const char *aval)
|
|
{
|
|
name = new char[strlen(aname)+1];
|
|
strcpy(name, aname);
|
|
if(aval) {
|
|
value = new char[strlen(aval)+1];
|
|
strcpy(value, aval);
|
|
} else {
|
|
value = 0;
|
|
}
|
|
next = 0;
|
|
}
|
|
|
|
IniFileParser::Parameter::~Parameter()
|
|
{
|
|
delete[] name;
|
|
if(value)
|
|
delete[] value;
|
|
if(next) delete next;
|
|
}
|
|
|
|
void IniFileParser::Parameter::AddDictionaryData(const char* a_val, bool excl)
|
|
{
|
|
int avl;
|
|
if(a_val) {
|
|
avl = strlen(a_val);
|
|
while(avl > 0 && is_space(a_val[avl-1]))
|
|
avl--;
|
|
}
|
|
if(excl || !value || value[0]=='\0') { // just replace with the new value
|
|
if(value)
|
|
delete[] value;
|
|
if(a_val) {
|
|
value = new char[avl+1];
|
|
strcpy(value, a_val);
|
|
value[avl] = 0;
|
|
} else {
|
|
value = 0;
|
|
}
|
|
return;
|
|
}
|
|
if(!a_val) // note we know current value is not empty
|
|
return;
|
|
|
|
// join values XXX must be made optional
|
|
int lv = strlen(value);
|
|
char *newval = new char[lv+1+avl+1];
|
|
strcpy(newval, value);
|
|
newval[lv] = ',';
|
|
strncpy(newval + lv + 1, a_val, avl);
|
|
newval[lv+1+avl] = 0;
|
|
delete[] value;
|
|
value = newval;
|
|
}
|
|
|
|
void IniFileParser::Parameter::AddDictionaryLine(const char *a_value)
|
|
{
|
|
int avl = strlen(a_value);
|
|
while(avl > 0 && is_space(a_value[avl-1]))
|
|
avl--;
|
|
|
|
if(!value) {
|
|
value = new char[avl+1];
|
|
strcpy(value, a_value);
|
|
return;
|
|
}
|
|
|
|
int lv = strlen(value);
|
|
char *newval = new char[lv+avl+2];
|
|
strcpy(newval, value);
|
|
newval[lv] = '\n';
|
|
strncpy(newval+lv+1, a_value, avl);
|
|
newval[lv+1+avl] = 0;
|
|
delete[] value;
|
|
value = newval;
|
|
}
|
|
|
|
|
|
char **IniFileParser::BreakParameterAsCSV(const char *param_text)
|
|
{
|
|
int len = 0, fld = 1;
|
|
enum { out, quoted, dq } state = out;
|
|
for(const char *s = param_text; *s; s++) {
|
|
switch(state) {
|
|
case out:
|
|
if(*s == '"')
|
|
state = quoted;
|
|
else
|
|
if(*s == ',') {
|
|
len++; fld++;
|
|
}
|
|
else
|
|
len++;
|
|
break;
|
|
case quoted:
|
|
if(*s == '"')
|
|
state = dq;
|
|
else
|
|
len++;
|
|
break;
|
|
case dq:
|
|
if(*s == '"') {
|
|
len++; state = quoted;
|
|
} else
|
|
if(*s == ',') {
|
|
len++; fld++;
|
|
state = out;
|
|
} else {
|
|
len++; state = out;
|
|
}
|
|
}
|
|
}
|
|
|
|
char **res = new char*[fld+1];
|
|
res[fld] = 0;
|
|
res[0] = new char[len+1];
|
|
int curf = 0, curch = 0;
|
|
|
|
state = out;
|
|
for(const char *s = param_text; *s; s++) {
|
|
switch(state) {
|
|
case out:
|
|
if(*s == '"')
|
|
state = quoted;
|
|
else
|
|
if(*s == ',') {
|
|
res[0][curch++] = 0;
|
|
res[++curf] = res[0] + curch;
|
|
}
|
|
else
|
|
res[0][curch++] = *s;
|
|
break;
|
|
case quoted:
|
|
if(*s == '"')
|
|
state = dq;
|
|
else
|
|
res[0][curch++] = *s;
|
|
break;
|
|
case dq:
|
|
if(*s == '"') {
|
|
res[0][curch++] = '"';
|
|
state = quoted;
|
|
} else
|
|
if(*s == ',') {
|
|
res[0][curch++] = 0;
|
|
res[++curf] = res[0] + curch;
|
|
state = out;
|
|
} else {
|
|
res[0][curch++] = *s;
|
|
state = out;
|
|
}
|
|
}
|
|
}
|
|
res[0][curch] = 0;
|
|
return res;
|
|
}
|
|
|
|
void IniFileParser::DisposeBrokenParameter(char** bparm)
|
|
{
|
|
delete[] bparm[0];
|
|
delete[] bparm;
|
|
}
|