00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 #include "data/config.hpp"
00035 #include "data/directory.hpp"
00036 #include "data/fstream.hpp"
00037
00038 #include <cwctype>
00039
00040 namespace gsgl
00041 {
00042
00043 namespace data
00044 {
00045
00046 config_record::config_record()
00047 : data_object(), parent(0), associated_var(0), f(0)
00048 {
00049 }
00050
00051
00052 config_record::config_record(const config_record & conf)
00053 : data_object(), parent(conf.parent), associated_var(conf.associated_var), f(conf.f)
00054 {
00055 children = conf.children;
00056 name = conf.name;
00057 text = conf.text;
00058 attributes = conf.attributes;
00059 }
00060
00061
00062 config_record::config_record(const string & fname)
00063 : data_object(), parent(0), associated_var(0), f(new io::file(fname))
00064 {
00065 if (io::file::exists(f->get_full_path()))
00066 {
00067 smart_pointer<io::ft_stream> s(f->open_text());
00068 from_stream(*s);
00069 }
00070 else
00071 {
00072 throw runtime_exception(L"Trying to open non-existent configuration file: %ls", fname.w_string());
00073 }
00074 }
00075
00076
00077 config_record & config_record::operator= (const config_record & conf)
00078 {
00079 parent = conf.parent;
00080 children = conf.children;
00081 name = conf.name;
00082 text = conf.text;
00083 attributes = conf.attributes;
00084 associated_var = conf.associated_var;
00085 f = conf.f;
00086
00087 return *this;
00088 }
00089
00090
00091 config_record::~config_record()
00092 {
00093 }
00094
00095
00096 bool config_record::operator== (const config_record & conf) const
00097 {
00098 return parent == conf.parent
00099 && children == conf.children
00100 && name == conf.name
00101 && text == conf.text
00102 && attributes == conf.attributes
00103 && f == conf.f;
00104 }
00105
00106
00107
00108
00109 const io::file & config_record::get_file() const
00110 {
00111 return *f;
00112 }
00113
00114
00115 const io::directory & config_record::get_directory() const
00116 {
00117 return f->get_directory();
00118 }
00119
00120
00121 const string & config_record::get_name() const
00122 {
00123 return name;
00124 }
00125
00126
00127 string & config_record::get_name()
00128 {
00129 return name;
00130 }
00131
00132
00133 const string & config_record::get_text() const
00134 {
00135 return text;
00136 }
00137
00138
00139 string & config_record::get_text()
00140 {
00141 return text;
00142 }
00143
00144
00145
00146
00147 const string & config_record::get_attribute(const string & a) const
00148 {
00149 return attributes[a];
00150 }
00151
00152
00153 string & config_record::get_attribute(const string & a)
00154 {
00155 return attributes[a];
00156 }
00157
00158
00159 const dictionary<gsgl::string, gsgl::string> & config_record::get_attributes() const
00160 {
00161 return attributes;
00162 }
00163
00164
00165 dictionary<gsgl::string, gsgl::string> & config_record::get_attributes()
00166 {
00167 return attributes;
00168 }
00169
00170
00171
00172
00173 config_record & config_record::find_child(gsgl::data::list<gsgl::string>::iterator & pos_in_path, bool create)
00174 {
00175 assert(pos_in_path.is_valid());
00176
00177 config_record *child = 0;
00178 string child_name = *pos_in_path;
00179 child_name.trim();
00180
00181
00182 for (list<config_record>::iterator i = children.iter(); i.is_valid(); ++i)
00183 {
00184 if (i->name == child_name)
00185 {
00186 child = &*i;
00187 break;
00188 }
00189 }
00190
00191
00192 if (!child)
00193 {
00194 if (create)
00195 {
00196 children.append(config_record());
00197 child = &children.get_tail();
00198 child->parent = this;
00199 child->name = *pos_in_path;
00200 child->name.trim();
00201 }
00202 else
00203 {
00204 throw runtime_exception(L"Invalid config path '%ls'.", pos_in_path->w_string());
00205 }
00206 }
00207
00208
00209 if ((++pos_in_path).is_valid())
00210 return child->find_child(pos_in_path, create);
00211 else
00212 return *child;
00213 }
00214
00215
00216
00217 const config_record & config_record::get_child(const string & path) const
00218 {
00219 list<string> path_list = path.split(L"/");
00220 list<string>::iterator pos = path_list.iter();
00221
00222 if (pos.is_valid())
00223 {
00224 return const_cast<config_record *>(this)->find_child(pos, false);
00225 }
00226 else
00227 {
00228 throw runtime_exception(L"Invalid config path '%ls'", path.w_string());
00229 }
00230 }
00231
00232
00233 config_record & config_record::get_child(const string & path)
00234 {
00235 list<string> path_list = path.split(L"/");
00236 list<string>::iterator pos = path_list.iter();
00237
00238 if (pos.is_valid())
00239 {
00240 return find_child(pos, true);
00241 }
00242 else
00243 {
00244 throw runtime_exception(L"Invalid config path '%ls'", path.w_string());
00245 }
00246 }
00247
00248
00249 const list<config_record> & config_record::get_children() const
00250 {
00251 return children;
00252 }
00253
00254
00255 list<config_record> & config_record::get_children()
00256 {
00257 return children;
00258 }
00259
00260
00261
00262
00263 void config_record::override_with(const config_record & cr)
00264 {
00265
00266 if (name != cr.name)
00267 throw runtime_exception(L"You cannot override a record named '%ls' with one named '%ls'!", name.w_string(), cr.name.w_string());
00268
00269 text = cr.text;
00270
00271
00272 for (dictionary<string, string>::const_iterator cr_att = cr.attributes.iter(); cr_att.is_valid(); ++cr_att)
00273 {
00274 attributes[cr_att.get_index()] = *cr_att;
00275 }
00276
00277
00278 dictionary<int, string> num_children_assigned;
00279
00280 for (list<config_record>::const_iterator cr_child = cr.children.iter(); cr_child.is_valid(); ++cr_child)
00281 {
00282
00283 int num_to_skip = num_children_assigned[cr_child->name];
00284 int num_seen = 0;
00285 list<config_record>::iterator my_child = children.iter();
00286 for (; my_child.is_valid(); ++my_child)
00287 {
00288 if (my_child->name == cr_child->name)
00289 {
00290 if (num_seen == num_to_skip)
00291 {
00292 my_child->override_with(*cr_child);
00293 ++num_children_assigned[cr_child->name];
00294 break;
00295 }
00296
00297 ++num_seen;
00298 }
00299 }
00300
00301
00302 if (!my_child.is_valid())
00303 {
00304 children.append(config_record());
00305 children.get_tail().parent = this;
00306 children.get_tail().name = cr_child->name;
00307 children.get_tail().override_with(*cr_child);
00308 }
00309 }
00310
00311
00312 if (associated_var)
00313 {
00314 associated_var->assign_from_string(text);
00315 }
00316 }
00317
00318
00319 void config_record::save()
00320 {
00321 if (parent)
00322 parent->save();
00323
00324 if (f)
00325 {
00326 smart_pointer<io::ft_stream> s(f->open_text(io::FILE_OPEN_WRITE));
00327 to_stream(*s);
00328 }
00329 else
00330 {
00331 throw runtime_exception(L"You cannot save a config record with no associated file.");
00332 }
00333 }
00334
00335
00336
00337
00338 static string quoted(const string & str)
00339 {
00340 string res;
00341 for (int i = 0; i < str.size(); ++i)
00342 {
00343 res.append(str[i]);
00344 if (str[i] == L'\\')
00345 res.append(L'\\');
00346 }
00347 return res;
00348 }
00349
00350
00351 void config_record::to_stream(io::text_stream & s) const
00352 {
00353 to_stream(s, 0);
00354 }
00355
00356
00357 void config_record::to_stream(io::text_stream & s, int indent) const
00358 {
00359 for (int i = 0; i < indent; ++i)
00360 s << L" ";
00361
00362 if (children.size() || text.size())
00363 {
00364 s << L"<" << name;
00365
00366 for (dictionary<string,string>::const_iterator i = attributes.iter(); i.is_valid(); ++i)
00367 s << L" " << i.get_index() << L"=\"" << quoted(*i) << L"\"";
00368
00369 s << L">";
00370
00371 if (text.size() > 32)
00372 {
00373 s << L"\n";
00374 for (int i = 0; i < indent+1; ++i)
00375 s << L" ";
00376 }
00377
00378 s << text;
00379
00380 if (children.size() || text.size() > 32)
00381 s << L"\n";
00382
00383 for (list<config_record>::const_iterator child = children.iter(); child.is_valid(); ++child)
00384 {
00385 child->to_stream(s, indent+1);
00386 }
00387
00388 if (children.size() || text.size() > 32)
00389 for (int i = 0; i < indent; ++i)
00390 s << L" ";
00391 s << L"</" << name << L">\n";
00392 }
00393 else
00394 {
00395 s << L"<" << name;
00396
00397 for (dictionary<string, string>::const_iterator i = attributes.iter(); i.is_valid(); ++i)
00398 s << L" " << i.get_index() << L"=\"" << quoted(*i) << L"\"";
00399
00400 s << L"/>\n";
00401 }
00402 }
00403
00404
00405
00406 static wchar_t get_char(io::text_stream & s, int & line)
00407 {
00408 wchar_t ch = s.get();
00409
00410 if (ch == L'\n')
00411 {
00412 ++line;
00413 }
00414 else if (ch == L'\r')
00415 {
00416 ++line;
00417 if (s.peek() == L'\n')
00418 ch = s.get();
00419 }
00420
00421 return ch;
00422 }
00423
00424 void config_record::from_stream(io::text_stream & s)
00425 {
00426 int line = 1;
00427 from_stream(s, line);
00428 }
00429
00430
00431 #define SYNTAX_ERROR(msg) throw runtime_exception(L"%ls (%d): syntax error: %ls", f->get_full_path().w_string(), line, msg)
00432 #define CHECK_EOF if (ch == WEOF) throw runtime_exception(L"%ls (%d): unexpected end of file", f->get_full_path().w_string(), line)
00433
00434 void config_record::from_stream(io::text_stream & s, int & line)
00435 {
00436 simple_array<wchar_t> buf(32);
00437 wchar_t ch;
00438
00439
00440 ch = get_char(s, line);
00441
00442 while (::iswspace(ch))
00443 ch = get_char(s, line);
00444
00445 if (ch == WEOF)
00446 return;
00447
00448 if (ch != L'<')
00449 SYNTAX_ERROR(L"missing '<'");
00450
00451
00452 buf.clear();
00453
00454 ch = get_char(s, line);
00455
00456 while (::iswalnum(ch) || ch == L'_' || ch == L':')
00457 {
00458 buf.append(ch);
00459 ch = get_char(s, line);
00460 }
00461
00462 buf.append(0);
00463 name = buf.ptr();
00464 name.trim();
00465
00466 CHECK_EOF;
00467 if (name.is_empty())
00468 SYNTAX_ERROR(L"empty element name");
00469
00470
00471 while (::iswspace(ch))
00472 {
00473 string att_name, att_val;
00474
00475
00476 buf.clear();
00477
00478 ch = get_char(s, line);
00479 while (::iswspace(ch))
00480 {
00481 ch = get_char(s, line);
00482 CHECK_EOF;
00483 }
00484
00485 if (ch == L'/' || ch == L'>')
00486 break;
00487
00488 while (::iswalnum(ch) || ch == L'_')
00489 {
00490 buf.append(ch);
00491 ch = get_char(s, line);
00492 CHECK_EOF;
00493 }
00494
00495 buf.append(0);
00496 att_name = buf.ptr();
00497 att_name.trim();
00498
00499 if (att_name.size() == 0)
00500 SYNTAX_ERROR(L"missing attribute name");
00501 if (ch != L'=')
00502 SYNTAX_ERROR(L"missing '='");
00503
00504 ch = get_char(s, line);
00505 if (ch != L'"')
00506 SYNTAX_ERROR(L"missing '\"'");
00507
00508
00509 buf.clear();
00510
00511 ch = get_char(s, line);
00512 while (ch != L'"')
00513 {
00514 if (ch == L'\\')
00515 {
00516 ch = get_char(s, line);
00517 if (ch == L'\n')
00518 ch = L' ';
00519 }
00520
00521 buf.append(ch);
00522 ch = get_char(s, line);
00523 CHECK_EOF;
00524 }
00525
00526 buf.append(0);
00527 att_val = buf.ptr();
00528 att_val.trim();
00529
00530 attributes[att_name] = att_val;
00531
00532
00533 ch = get_char(s, line);
00534 CHECK_EOF;
00535 }
00536
00537 bool no_body = false;
00538 if ((no_body = ch == L'/'))
00539 ch = get_char(s, line);
00540
00541 if (ch != L'>')
00542 SYNTAX_ERROR(L"missing '>'");
00543
00544 if (no_body)
00545 return;
00546
00547
00548 bool in_space = false;
00549 while (true)
00550 {
00551 ch = s.peek();
00552 CHECK_EOF;
00553
00554 if (ch == L'<')
00555 {
00556 wchar_t brak = get_char(s, line);
00557
00558 ch = s.peek();
00559 CHECK_EOF;
00560
00561 if (ch == L'/')
00562 {
00563 ch = get_char(s, line);
00564 CHECK_EOF;
00565
00566 buf.clear();
00567
00568 ch = get_char(s, line);
00569 CHECK_EOF;
00570
00571 while (ch != L'>')
00572 {
00573 buf.append(ch);
00574
00575 ch = get_char(s, line);
00576 CHECK_EOF;
00577 }
00578
00579 buf.append(0);
00580 string end_name = buf.ptr();
00581 end_name.trim();
00582
00583 if (this->name != end_name)
00584 SYNTAX_ERROR(L"mismatched end tag");
00585 break;
00586 }
00587 else if (ch == L'!')
00588 {
00589
00590 ch = get_char(s, line);
00591 CHECK_EOF;
00592 if (ch != L'-')
00593 SYNTAX_ERROR(L"missing '-' in comment");
00594
00595 ch = get_char(s, line);
00596 CHECK_EOF;
00597 if (ch != L'-')
00598 SYNTAX_ERROR(L"missing '-' in comment");
00599
00600 int dash_count = 0;
00601
00602 do
00603 {
00604 ch = get_char(s, line);
00605 CHECK_EOF;
00606
00607 if (ch == L'-')
00608 ++dash_count;
00609 else
00610 dash_count = 0;
00611 }
00612 while (!(ch == L'>' && dash_count >= 2));
00613 }
00614 else
00615 {
00616 s.unget(brak);
00617
00618 children.append(config_record());
00619 config_record & child = children.get_tail();
00620 child.parent = this;
00621 child.f = f;
00622 child.from_stream(s, line);
00623
00624 continue;
00625 }
00626 }
00627 else if (::iswspace(ch))
00628 {
00629 in_space = true;
00630 get_char(s, line);
00631 CHECK_EOF;
00632 }
00633 else
00634 {
00635 CHECK_EOF;
00636
00637 if (in_space)
00638 {
00639 this->text.append(L' ');
00640 in_space = false;
00641 }
00642
00643 this->text.append(get_char(s, line));
00644 }
00645 }
00646
00647 this->text.trim();
00648 }
00649
00650
00651
00652
00653 config_record global_config::global_config_vars;
00654
00655
00656 global_config::global_config(const gsgl::string & path)
00657 : associated_record(global_config_vars.get_child(path))
00658 {
00659 assert(!associated_record.associated_var);
00660 associated_record.associated_var = this;
00661 }
00662
00663
00664 global_config::~global_config()
00665 {
00666
00667
00668 }
00669
00670
00671 void global_config::override_with(const config_record & cr)
00672 {
00673 global_config_vars.override_with(cr);
00674 }
00675
00676
00677 void global_config::save(const string & fname)
00678 {
00679 config_record temp(fname);
00680 temp.override_with(global_config_vars);
00681 temp.save();
00682 }
00683
00684
00685
00686
00687 config_variable<gsgl::string> CONFIG_TEST_ZERO(L"test/zero", L"0");
00688 config_variable<float> CONFIG_TEST_ONE(L"test/one", 3.14f);
00689 config_variable<int> CONFIG_TEST_TWO(L"test/two", 2);
00690
00691 }
00692
00693 }