1 
2 /* "Species" - a CoreWars evolver.  Copyright (C) 2003 'Varfar'
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 1, or (at your option) any later
7  * version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include "ini.hpp"
20 
21 #include <string.h>
22 
23 using namespace std;
24 
25 namespace { // anonymous
26 
27 	const char *KVP_ERRORS[KeyValuePair::KVP_ERROR_LAST] = {
28     	"VALUE_NOT_INT","VALUE_NOT_BOOL","VALUE_NOT_FLOAT",
29     	"VALUE_NOT_DOUBLE","KEY_NOT_VALID","KEY_HAS_NO_CATEGORY"
30 	};
31 
str_equals(const string str1,const char * s2)32 	bool str_equals(const string str1,const char *s2) {
33 		const char *s1 = str1.data();
34 		unsigned int s11, s12, s21, s22;
35 		for(s11=0; s11<strlen(s1); s11++) if(s1[s11] > ' ') break;
36 		for(s12=strlen(s1)-1; s12 > s11; s11--) if(s1[s12] > ' ') break;
37 		for(s21=0; s21<strlen(s2); s21++) if(s2[s21] > ' ') break;
38 		for(s22=strlen(s2)-1; s22 > s21; s22--) if(s2[s22] > ' ') break;
39 		// easy length check
40 		if((s12-s11) != (s22-s21)) return false;
41 		// ok, have to compare each character
42 		return (strncmp(&s1[s11],&s2[s21],s12-s11) == 0);
43 	}
44 
45 } // end of anon namespace
46 
KeyValuePair()47 KeyValuePair::KeyValuePair() {
48    linenum = 0;
49    valid = false;
50 }
51 
~KeyValuePair()52 KeyValuePair::~KeyValuePair() {}
53 
ERROR_MSG(const KVP_ERROR err)54 const char *KeyValuePair::ERROR_MSG(const KVP_ERROR err) {
55 	if(0 <= err < KVP_ERROR_LAST)
56 		return KVP_ERRORS[err];
57 	return 0;
58 }
59 
60 // checking key
61 
is(const char * key) const62 bool KeyValuePair::is(const char *key) const {
63    if(!valid) return false; // obviously
64    return (strcasecmp(this->key,key) == 0);
65 }
66 
67 // getting key
68 
getKeyAsString(string & key)69 void KeyValuePair::getKeyAsString(string &key) {
70    if(!valid) throw KEY_NOT_VALID;
71    key = string(this->key); // reconstruct
72 }
73 
getKeyAsChar()74 const char *KeyValuePair::getKeyAsChar() {
75    if(!valid) throw KEY_NOT_VALID;
76    return key;
77 }
78 
79 // checking category
80 
isCategory(const char * category)81 bool KeyValuePair::isCategory(const char *category) {
82    if(!valid) return false; // obviously
83    if(!hascategory) return false; // obviously
84    return (strcasecmp(this->category,category) == 0);
85 }
86 
hasSubCategory()87 bool KeyValuePair::hasSubCategory() { // more categories?  category.category...key
88    for(char *ch=key; *ch != '\0'; ch++)
89       if(*ch == '.') return true;
90    return false;
91 }
92 
getSubCategory()93 bool KeyValuePair::getSubCategory() { // will divide the current key into category.key
94    hascategory = false;
95    char *ch;
96    for(ch = key; *ch != '\0'; ch++)
97       if(*ch == '.') { // find dot delimiter
98          hascategory = true;
99          break;
100       }
101    if(hascategory) {
102       category = key;
103       *ch = '\0';
104       key = ++ch;
105       //cout << "category = \"" << category << "\"\tkey=\"" << key << '\"' << endl;
106       if(*key == '\0') { // empty key now?  was trailing . ?
107          // repair damage!
108          hascategory = false;
109          *--ch = '.';
110          key = category;
111       }
112    } else
113       category = NULL;
114    return hascategory;
115 }
116 
117 // getting category
118 
getCategoryAsString(string & category)119 void KeyValuePair::getCategoryAsString(string &category) {
120    if(!valid) throw KEY_NOT_VALID;
121    if(!hascategory) throw KEY_HAS_NO_CATEGORY;
122    category = string(this->category); // reconstruct
123 }
124 
getCategoryAsChar() const125 const char *KeyValuePair::getCategoryAsChar() const {
126    if(!valid) throw KEY_NOT_VALID;
127    if(!hascategory) throw KEY_HAS_NO_CATEGORY;
128    return category;
129 }
130 
131 // checking value
132 
isValue(const char * val) const133 bool KeyValuePair::isValue(const char *val) const {
134 	if(!valid) return false; // obviously
135 	return (strcasecmp(this->value,val) == 0);
136 }
137 
138 // getting value
139 
getValueAsString(string & value)140 void KeyValuePair::getValueAsString(string &value) {
141    if(!valid) throw KEY_NOT_VALID;
142    value = string(this->value); // reconstruct
143 }
144 
getValueAsChar()145 const char *KeyValuePair::getValueAsChar() {
146    if(!valid) throw KEY_NOT_VALID;
147    return value;
148 }
149 
getValueAsInt(int def)150 int KeyValuePair::getValueAsInt(int def) {
151    // Will's patent parser detects errors better than atoi
152    if(!valid) throw KEY_NOT_VALID;
153    char *ch = value;
154    if('\0' == *ch) return def;
155    bool neg = (*ch=='-'); // remember if negative
156    if(*ch=='+' || *ch=='-') // step past sign?
157       ch++;
158    int result = 0;
159    while(*ch>='0' && *ch<='9') {
160       int digit = (*ch)-'0'; // 0..9
161       result *= 10;
162       result += digit;
163       ch++;
164    }
165    if(*ch!='\0') throw VALUE_NOT_INT;
166    if(neg)
167       result = -result;
168    return result;
169 }
170 
getValueAsFloat(float def,bool percent_allowed)171 float KeyValuePair::getValueAsFloat(float def,bool percent_allowed) {
172    // Will's patent parser detects errors better than atof
173    if(!valid) throw KEY_NOT_VALID;
174    char *ch = value;
175    if('\0' == *ch) return def;
176    bool neg = (*ch=='-'); // remember if negative
177    if(*ch=='+' || *ch=='-') // step past sign?
178       ch++;
179    float result = 0.0f;
180    int digit;
181    while(*ch>='0' && *ch<='9') {
182       digit = (*ch)-'0'; // 0..9
183       result *= 10;
184       result += digit;
185       ch++;
186    }
187    if(*ch=='.' || *ch==',') { // floating point?  Note European-friendly :)
188       ch++; // step past it
189       float weight = 1.0f;
190       while(*ch>='0' && *ch<='9') {
191          digit = (*ch)-'0'; // 0..9
192          weight *= 10.0f;
193          result += digit/weight;
194          ch++;
195       }
196    }
197    if(*ch=='%') {
198       if(percent_allowed)
199          result = result / 100.0f;
200       else
201       	throw VALUE_NOT_FLOAT;
202       ch++; // step over it
203    }
204    if(*ch!='\0')
205    	throw VALUE_NOT_FLOAT;
206    if(neg)
207       result = -result;
208    return result;
209 }
210 
getValueAsDouble(double def,bool percent_allowed)211 double KeyValuePair::getValueAsDouble(double def,bool percent_allowed) {
212    // Will's patent parser detects errors better than atof
213    if(!valid) throw KEY_NOT_VALID;
214    char *ch = value;
215    if('\0' == *ch) return def;
216    bool neg = (*ch=='-'); // remember if negative
217    if(*ch=='+' || *ch=='-') // step past sign?
218       ch++;
219    double result = 0.0;
220    int digit;
221    while(*ch>='0' && *ch<='9') {
222       digit = (*ch)-'0'; // 0..9
223       result *= 10;
224       result += digit;
225       ch++;
226    }
227    if(*ch=='.' || *ch==',') { // floating point?  Note European-friendly :)
228       ch++; // step past it
229       float weight = 1.0;
230       while(*ch>='0' && *ch<='9') {
231          digit = (*ch)-'0'; // 0..9
232          weight *= 10.0;
233          result += digit/weight;
234          ch++;
235       }
236    }
237    if(*ch=='%') {
238       if(percent_allowed)
239          result = result / 100.0;
240       else
241       	throw VALUE_NOT_DOUBLE;
242       ch++; // step over it
243    }
244    if(*ch!='\0')
245    	throw VALUE_NOT_DOUBLE;
246 	if(neg)
247       result = -result;
248    return result;
249 }
250 
getValueAsBool(bool def)251 bool KeyValuePair::getValueAsBool(bool def) {
252    if(!valid) throw KEY_NOT_VALID;
253    if('\0' == *value) return def;
254    if(strcasecmp(value,"true")==0 || strcasecmp(value,"on")==0 || strcasecmp(value,"1")==0 || strcasecmp(value,"yes")==0)
255       return true;
256    if(strcasecmp(value,"false")==0 || strcasecmp(value,"off")==0 || strcasecmp(value,"0")==0 || strcasecmp(value,"no")==0)
257       return false;
258    throw VALUE_NOT_BOOL;
259 }
260 
261 // read line from input stream
262 
263 // uses very c-style parsing techniques with character pointers
fromSerial(istream & is)264 istream &KeyValuePair::fromSerial(istream &is) {
265    valid = false;
266    while(!is.eof()) {
267       // get the line
268       is.getline(line,MAX_KVP_LINE_LEN);
269       linenum++;
270       //cout << "line #" << linenum << " is \"" << line << '\"' << endl;
271       // trim leading whitespace
272       key = line;
273       while(*key==' ' || *key=='\t')
274          key++;
275       // is blank line?
276       if(*key=='\0' || *key=='\r' || *key==';' || *key=='#') // empty or full-line comment?
277          continue; // try again
278       // is another section?
279       if(*key=='[')
280       	break; // abort parse
281       // truncate at any trailing comment
282       char *end = key;
283       while(*end!=';' && *end!='#' && *end!='\0' && *end!='\r')
284          end++;
285       *end = '\0'; // do truncate
286       // work out split between key and value
287       value = key;
288       while(*value!='=' && *value!='\0')
289          value++;
290       if(*value=='\0') // no separator?
291          continue;
292       // trim key
293       char *ch = value;
294       while(ch>key && (*ch==' ' || *ch=='\t' || *ch=='='))
295          ch--;
296       ch++;
297       *ch = '\0'; // truncate; key done
298       if(*key=='\0') // check again
299          continue;
300       // check key for illegal characters
301       bool illegal = false;
302       for(ch=key; *ch != '\0'; ch++)
303          if(!((*ch>='A' && *ch<='Z') || (*ch>='a' && *ch<='z') ||
304             (*ch>='0' && *ch<='9') || *ch=='.' || *ch=='-' || *ch=='_')) {
305             cerr << "ini: illegal char (" << int(*ch) << ") in key on line " << linenum << endl;
306             illegal = true;
307             break;
308          }
309       if(illegal) continue;
310       // trim whitespace from leading value
311       value++; // move past =
312       while(*value==' ' || *value=='\t')
313          value++;
314       if(*value=='\0') { // value is empty?
315          cerr << "ini: value is empty on line " << linenum << " (" << key << ')' << endl;
316          continue;
317       }
318       // trim whitespace from trailing value
319       ch = end;
320       ch--;
321       while(ch>value && (*ch==' ' || *ch=='\t'))
322          ch--;
323       ch++;
324       *ch = '\0'; // truncate here too
325       // check value for illegal characters
326       illegal = false;
327       for(ch=value; *ch != '\0'; ch++)
328          if(*ch<32 || *ch>126) { // 7-bit printable ASCII
329             cerr << "ini: illegal char (" << int(*ch) << ") in value on line " << linenum << endl;
330             illegal = true;
331             break;
332          }
333       if(illegal) continue;
334       // category in key?
335       getSubCategory();
336       // done
337       valid = true;
338       break;
339    }
340    return is;
341 }
342 
343 /******* INIFile class implementation ***********************/
344 
INIFile(istream & is)345 INIFile::INIFile(istream &is): _is(&is), _section_ofs(NO_SECTION), _kvp() {
346 	// find out if we have an empty stream
347 	_is->seekg(0,ios::end); // go to the bottom of the stream
348 	_bad_stream = (0 >= _is->tellg());
349 }
350 
seek(const string & section)351 bool INIFile::seek(const string &section) { // returns success
352 	if(_bad_stream)
353 		return false;
354 	string wanted = "["; wanted += section; wanted += ']';
355 	_is->clear(); // clear any flags
356 	_is->seekg(0,ios::beg); // go to top of stream
357 	while(!_is->eof()) {
358 		_is->getline(_section,KeyValuePair::MAX_KVP_LINE_LEN);
359 		//cout << "x: " << _section << endl;
360 		if(str_equals(wanted,_section)) { // found?
361 			_section_ofs = _is->tellg();
362 			_section_ofs--; // just step back a bit to try to fix obscure bug
363 			return true;
364 		}
365 	}
366 	_section_ofs = NO_SECTION;
367 	return false; // not found
368 }
369 
get(const string & key)370 KeyValuePair *INIFile::get(const string &key) { // returns null if no such key
371 	if(NO_SECTION == _section_ofs) // bad section?
372 		return 0;
373 	_is->clear(); // clear any flags
374 	_is->seekg(_section_ofs,ios::beg); // seek to beginning of section data
375 	_kvp = KeyValuePair();
376 	*_is >> _kvp;
377 	while(_kvp.isValid()) {
378 		if(_kvp.is(key)) // match?
379 			return &_kvp;
380 		*_is >> _kvp; // next
381 	}
382 	return 0; // bad
383 }
384 
385