1 #include <cstdlib>
2 #include <algorithm>
3 #include "xr_file_system.h"
4 #include "xr_ini_file.h"
5 #include "xr_clsid.h"
6 #include "xr_string_utils.h"
7 #include "xr_utils.h"
8 
9 using namespace xray_re;
10 
11 enum {
12 	IF_EOL	= 0x100,
13 	IF_EOF	= 0x101,
14 };
15 
~xr_ini_file()16 xr_ini_file::~xr_ini_file()
17 {
18 	clear();
19 }
20 
21 struct xr_ini_file::ini_section_pred {
22 	const char* name;
ini_section_predxr_ini_file::ini_section_pred23 	explicit ini_section_pred(const char* _name): name(_name) {}
operator ()xr_ini_file::ini_section_pred24 	bool operator()(const ini_section* l) const { return xr_stricmp(l->name.c_str(), name) < 0; }
25 };
26 
27 struct xr_ini_file::ini_item_pred {
28 	const char* name;
ini_item_predxr_ini_file::ini_item_pred29 	explicit ini_item_pred(const char* _name): name(_name) {}
operator ()xr_ini_file::ini_item_pred30 	bool operator()(const ini_item* l) const { return xr_stricmp(l->name.c_str(), name) < 0; }
31 };
32 
clear()33 void xr_ini_file::clear()
34 {
35 	trim_container(m_sections);
36 }
37 
operator <(const ini_item & right) const38 inline bool xr_ini_file::ini_item::operator<(const ini_item& right) const
39 {
40 	return xr_stricmp(name.c_str(), right.name.c_str()) < 0;
41 }
42 
~ini_section()43 xr_ini_file::ini_section::~ini_section()
44 {
45 	delete_elements(items);
46 }
47 
r_section(const char * sname) const48 const xr_ini_file::ini_section* xr_ini_file::r_section(const char* sname) const
49 {
50 	ini_section_vec_cit it = lower_bound_if(m_sections.begin(), m_sections.end(), ini_section_pred(sname));
51 	if (it == m_sections.end() || xr_stricmp((*it)->name.c_str(), sname) != 0) {
52 		msg("can't find section %s", sname);
53 		xr_not_expected();
54 	}
55 	return *it;
56 }
57 
line_exist(const char * lname,const char ** lvalue) const58 bool xr_ini_file::ini_section::line_exist(const char* lname, const char** lvalue) const
59 {
60 	ini_item_vec_cit it = lower_bound_if(begin(), end(), ini_item_pred(lname));
61 	if (it == end() || xr_stricmp((*it)->name.c_str(), lname) != 0)
62 		return false;
63 	if (lvalue)
64 		*lvalue = (*it)->value.c_str();
65 	return true;
66 }
67 
line_exist(const char * sname,const char * lname) const68 bool xr_ini_file::line_exist(const char* sname, const char* lname) const
69 {
70 	ini_section_vec_cit it = lower_bound_if(m_sections.begin(), m_sections.end(), ini_section_pred(sname));
71 	if (it == m_sections.end() || xr_stricmp((*it)->name.c_str(), sname) != 0)
72 		return false;
73 	return (*it)->line_exist(lname, 0);
74 }
75 
line_count(const char * sname) const76 size_t xr_ini_file::line_count(const char* sname) const
77 {
78 	return r_section(sname)->size();
79 }
80 
section_exist(const char * sname) const81 bool xr_ini_file::section_exist(const char* sname) const
82 {
83 	ini_section_vec_cit it = lower_bound_if(m_sections.begin(), m_sections.end(), ini_section_pred(sname));
84 	return it != m_sections.end() && xr_stricmp((*it)->name.c_str(), sname) == 0;
85 }
86 
r_clsid(const char * sname,const char * lname) const87 uint64_t xr_ini_file::r_clsid(const char* sname, const char* lname) const
88 {
89 	return xr_clsid::to_quad(r_string(sname, lname));
90 }
91 
r_string(const char * sname,const char * lname) const92 const char* xr_ini_file::r_string(const char* sname, const char* lname) const
93 {
94 	const ini_section* section = r_section(sname);
95 	ini_item_vec_cit it = lower_bound_if(section->begin(), section->end(), ini_item_pred(lname));
96 	if (it == section->end() || xr_stricmp((*it)->name.c_str(), lname) != 0) {
97 		msg("can't find item %s in section %s", lname, sname);
98 		xr_not_expected();
99 	}
100 	return (*it)->value.c_str();
101 }
102 
is_true(const char * value)103 bool xr_ini_file::is_true(const char* value)
104 {
105 	if (xr_stricmp(value, "true") == 0 || xr_stricmp(value, "on") == 0 ||
106 			xr_stricmp(value, "yes") == 0 || std::strcmp(value, "1") == 0) {
107 		return true;
108 	}
109 	return false;
110 }
111 
r_bool(const char * sname,const char * lname) const112 bool xr_ini_file::r_bool(const char* sname, const char* lname) const
113 {
114 	return is_true(r_string(sname, lname));
115 }
116 
r_float(const char * sname,const char * lname) const117 float xr_ini_file::r_float(const char* sname, const char* lname) const
118 {
119 	return float(std::atof(r_string(sname, lname)));
120 }
121 
r_line(const char * sname,size_t lindex,const char ** lname,const char ** lvalue) const122 bool xr_ini_file::r_line(const char* sname, size_t lindex, const char** lname, const char** lvalue) const
123 {
124 	const ini_section* section = r_section(sname);
125 	if (lindex >= section->size())
126 		return false;
127 	const ini_item* item = section->items[lindex];
128 	if (lname)
129 		*lname = item->name.c_str();
130 	if (lvalue)
131 		*lvalue = item->value.c_str();
132 	return true;
133 }
134 
merge(const ini_section * section)135 void xr_ini_file::ini_section::merge(const ini_section* section)
136 {
137 	if (items.empty()) {
138 		items.reserve(section->size());
139 		for (ini_item_vec_cit it = section->begin(), last = section->end();
140 				it != last; ++it) {
141 			items.push_back(new ini_item(**it));
142 		}
143 	} else {
144 		// FIXME: do it O(max(M, N)) instead of O(M*log(N))
145 		for (ini_item_vec_cit it = section->begin(), last = section->end();
146 				it != last; ++it) {
147 			const ini_item* item = *it;
148 			ini_item_vec_it it1 = std::lower_bound(begin(), end(), item, ptr_less<ini_item>());
149 			if (it1 == end() || xr_stricmp((*it1)->name.c_str(), item->name.c_str()) != 0)
150 				items.insert(it1, new ini_item(*item));
151 			else
152 				(*it1)->value = item->value;
153 		}
154 	}
155 }
156 
is_name(int c)157 static inline bool is_name(int c)
158 {
159 	return std::isalnum(c) || std::strchr("@$_-?:.\\", c) != 0;
160 }
161 
is_eol(int c)162 static bool is_eol(int c)
163 {
164 	return c == '\n' || c == '\r';
165 }
166 
skip_blank(const char ** pp,const char * end)167 static int skip_blank(const char** pp, const char* end)
168 {
169 	for (const char* p = *pp; p != end; ++p) {
170 		int c = *p;
171 		if (c == ' ' || c == '\t') {
172 			// skip whitespace
173 			continue;
174 		}
175 		if (c == ';' || (c == '/' && (p + 1) != end && *(p + 1) == '/')) {
176 			// skip comment
177 			for (++p;; ++p) {
178 				if (p == end)
179 					goto eof_reached;
180 				c = *p;
181 				if (is_eol(c)) {
182 					if ((p + 1) != end && *(p + 1) == '\n')
183 						p++;
184 					*pp = p;
185 					return IF_EOL;
186 				}
187 			}
188 		}
189 		if (is_eol(c) && (p + 1) != end && *(p + 1) == '\n')
190 			p++;
191 		*pp = p;
192 		return is_eol(c)? IF_EOL : c;
193 	}
194 eof_reached:
195 	*pp = end;
196 	return IF_EOF;
197 }
198 
read_name(const char ** pp,const char * end,size_t buf_size,char * buf)199 static int read_name(const char **pp, const char* end, size_t buf_size, char* buf)
200 {
201 	xr_assert(buf_size != 0);
202 	int c = skip_blank(pp, end);
203 	if (c == IF_EOL) {
204 		*buf = '\0';
205 		return c;
206 	}
207 	--buf_size;
208 	for (const char* p = *pp; p != end; ++p) {
209 		c = *p;
210 		if (is_name(c)) {
211 			if (buf_size) {
212 				*buf++ = char(c);
213 				--buf_size;
214 			} else {
215 				xr_not_expected();
216 			}
217 			continue;
218 		}
219 		*buf = '\0';
220 		*pp = p;
221 		return skip_blank(pp, end);
222 	}
223 	*buf = '\0';
224 	*pp = end;
225 	return IF_EOF;
226 }
227 
read_item(const char ** pp,const char * end,std::string & buf,bool left)228 static int read_item(const char** pp, const char* end, std::string& buf, bool left)
229 {
230 	int c = skip_blank(pp, end);
231 	if (c == IF_EOL) {
232 		buf.clear();
233 		return c;
234 	}
235 	const char* value = *pp;
236 	const char* value_end = 0;
237 	for (const char* p = value;; ++p) {
238 		if (p == end) {
239 			if (value_end == 0)
240 				value_end = end;
241 			*pp = end;
242 			break;
243 		}
244 		c = *p;
245 		if (c == ' ' || c =='\t') {
246 			if (value_end == 0)
247 				value_end = p;
248 		} else if (c == '\n' || c == '\r' || c == ';' || (!left && c == '#') || (left && c == '=')) {
249 			if (value_end == 0)
250 				value_end = p;
251 			*pp = p;
252 			break;
253 		} else {
254 			value_end = 0;
255 		}
256 	}
257 	xr_assert(value_end);
258 	buf.assign(value, value_end);
259 	return skip_blank(pp, end);
260 }
261 
parse(const char * p,const char * end,const char * path)262 bool xr_ini_file::parse(const char* p, const char* end, const char* path)
263 {
264 	std::string folder, fname, extension;
265 	xr_file_system::split_path(path, &folder, &fname, &extension);
266 	fname.append(extension);
267 	const char* file = fname.c_str();
268 
269 	char temp[256];
270 	ini_section* section = 0;
271 	ini_item* item;
272 	std::string name;
273 	for (unsigned line = 1;; ++line) {
274 		int c = skip_blank(&p, end);
275 		xr_assert(p < end || c == IF_EOF);
276 		xr_assert(c != '\n' && c != '\r');
277 		if (c == '[') {
278 			++p;
279 			c = read_name(&p, end, sizeof(temp), temp);
280 			if (c != ']' || temp[0] == 0) {
281 				msg("bad section header at %s:%u", file, line);
282 				return false;
283 			}
284 			ini_section_vec_it it = lower_bound_if(m_sections.begin(), m_sections.end(), ini_section_pred(temp));
285 			if (it != m_sections.end() && xr_stricmp((*it)->name.c_str(), temp) == 0) {
286 				msg("duplicate section %s at %s:%u", temp, file, line);
287 				return false;
288 			}
289 			section = new ini_section(temp);
290 			m_sections.insert(it, section);
291 			++p;
292 			c = skip_blank(&p, end);
293 			if (c == ':') {
294 				for (;;) {
295 					++p;
296 					c = read_name(&p, end, sizeof(temp), temp);
297 					it = lower_bound_if(m_sections.begin(), m_sections.end(), ini_section_pred(temp));
298 					if (it == m_sections.end() || xr_stricmp((*it)->name.c_str(), temp) != 0) {
299 						msg("bad section reference '%s' at %s:%u", temp, file, line);
300 						break;
301 					}
302 					section->merge(*it);
303 					if (c != ',')
304 						break;
305 				}
306 			}
307 		} else if (c != IF_EOL && c != IF_EOF && is_name(c)) {
308 			if (section == 0) {
309 				msg("item without section at %s:%u", file, line);
310 				xr_not_expected();
311 			}
312 			c = read_item(&p, end, name, true);
313 			ini_item_vec_it it = lower_bound_if(section->begin(), section->end(), ini_item_pred(name.c_str()));
314 			if (it != section->end() && xr_stricmp((*it)->name.c_str(), name.c_str()) == 0) {
315 				item = *it;
316 			} else {
317 				item = new ini_item(name);
318 				section->items.insert(it, item);
319 			}
320 			if (c == '=') {
321 				++p;
322 				c = read_item(&p, end, item->value, false);
323 			} else {
324 				item->value.clear();
325 			}
326 		} else if (c == '#') {
327 			++p;
328 			c = read_name(&p, end, sizeof(temp), temp);
329 			if (c != '\"' || std::strcmp(temp, "include") != 0) {
330 				msg("bad directive %s at %s:%u", temp, file, line);
331 				return false;
332 			}
333 			++p;
334 			c = read_name(&p, end, sizeof(temp), temp);
335 			if (c != '\"' || temp[0] == 0) {
336 				msg("bad include at %s:%u", file, line);
337 				return false;
338 			}
339 
340 			if (!load_include((folder + temp).c_str()))
341 				return false;
342 
343 			++p;
344 			c = skip_blank(&p, end);
345 		}
346 		if (c == IF_EOF) {
347 			break;
348 		} else if (c != IF_EOL) {
349 			msg("ignoring trailing garbage at %s:%u", file, line);
350 			while (p != end) {
351 				if (*p++ == '\n')
352 					break;
353 			}
354 		} else {
355 			++p;
356 		}
357 	}
358 	return true;
359 }
360 
load_include(const char * path)361 bool xr_ini_file::load_include(const char* path)
362 {
363 	xr_file_system& fs = xr_file_system::instance();
364 	xr_reader* r = fs.r_open(path);
365 	if (r == 0) {
366 		msg("can't include %s", path);
367 		return false;
368 	}
369 	const char* p = r->pointer<const char>();
370 	bool status = parse(p, p + r->size(), path);
371 	fs.r_close(r);
372 	if (status)
373 		return true;
374 	msg("can't parse %s", path);
375 	return false;
376 }
377 
load(xr_reader & r)378 bool xr_ini_file::load(xr_reader& r)
379 {
380 	const char* p = r.pointer<const char>();
381 	if (parse(p, p + r.size(), "embedded"))
382 		return true;
383 	clear();
384 	return false;
385 }
386 
load(const char * path)387 bool xr_ini_file::load(const char* path)
388 {
389 	xr_file_system& fs = xr_file_system::instance();
390 	xr_reader* r = fs.r_open(path);
391 	if (r == 0)
392 		return false;
393 	const char* p = r->pointer<const char>();
394 	bool status = parse(p, p + r->size(), path);
395 	fs.r_close(r);
396 	if (status)
397 		return true;
398 	clear();
399 	return false;
400 }
401 
load(const char * path,const char * name)402 bool xr_ini_file::load(const char* path, const char* name)
403 {
404 	xr_file_system& fs = xr_file_system::instance();
405 	std::string full_path;
406 	if (!fs.resolve_path(path, name, full_path))
407 		return false;
408 	return load(full_path.c_str());
409 }
410