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