xoreos  0.0.5
configfile.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 // Inspired by ScummVM's config file and manager code
22 
27 #include <cassert>
28 
29 #include "src/common/error.h"
30 #include "src/common/encoding.h"
31 #include "src/common/scopedptr.h"
32 #include "src/common/readstream.h"
33 #include "src/common/writestream.h"
34 #include "src/common/strutil.h"
35 
36 #include "src/common/configfile.h"
37 
38 namespace Common {
39 
40 ConfigDomain::ConfigDomain(const UString &name) : _name(name) {
41 }
42 
44 }
45 
47  return _name;
48 }
49 
50 bool ConfigDomain::hasKey(const UString &key) const {
51  return _keys.find(key) != _keys.end();
52 }
53 
54 bool ConfigDomain::getKey(const UString &key, UString &value) const {
55  StringIMap::const_iterator k = _keys.find(key);
56  if (k == _keys.end())
57  return false;
58 
59  value = k->second;
60  return true;
61 }
62 
63 UString ConfigDomain::getString(const UString &key, const UString &def) const {
64  UString value;
65  if (!getKey(key, value))
66  return def;
67 
68  return value;
69 }
70 
71 bool ConfigDomain::getBool(const UString &key, bool def) const {
72  UString value;
73  if (!getKey(key, value))
74  return def;
75 
76  bool x = def;
77  try {
78  parseString(value, x);
79  } catch (...) {
81  }
82 
83  return x;
84 }
85 
86 int ConfigDomain::getInt(const UString &key, int def) const {
87  UString value;
88  if (!getKey(key, value))
89  return def;
90 
91  int x = def;
92  try {
93  parseString(value, x);
94  } catch (...) {
96  }
97 
98  return x;
99 }
100 
101 uint ConfigDomain::getUint(const UString &key, uint def) const {
102  UString value;
103  if (!getKey(key, value))
104  return def;
105 
106  uint x = def;
107  try {
108  parseString(value, x);
109  } catch (...) {
111  }
112 
113  return x;
114 }
115 
116 double ConfigDomain::getDouble(const UString &key, double def) const {
117  UString value;
118  if (!getKey(key, value))
119  return def;
120 
121  double x = def;
122  try {
123  parseString(value, x);
124  } catch (...) {
126  }
127 
128  return x;
129 }
130 
131 void ConfigDomain::setKey(const UString &key, const UString &value) {
132  StringIMap::iterator k = _keys.find(key);
133  if (k != _keys.end()) {
134  // Key already exists in the domain, replace the value
135 
136  k->second = value;
137  return;
138  }
139 
140  // Key doesn't yet exist in the domain, add it
141 
142  std::pair<StringIMap::iterator, bool> result =
143  _keys.insert(std::make_pair(key, value));
144 
145  _lines.push_back(Line());
146  _lines.back().key = result.first;
147 }
148 
149 void ConfigDomain::setString(const UString &key, const UString &value) {
150  setKey(key, value);
151 }
152 
153 void ConfigDomain::setBool(const UString &key, bool value) {
154  setKey(key, composeString(value));
155 }
156 
157 void ConfigDomain::setInt(const UString &key, int value) {
158  setKey(key, composeString(value));
159 }
160 
161 void ConfigDomain::setUint(const UString &key, uint value) {
162  setKey(key, composeString(value));
163 }
164 
165 void ConfigDomain::setDouble(const UString &key, double value) {
166  setKey(key, composeString(value));
167 }
168 
170  StringIMap::iterator k = _keys.find(key);
171  if (k == _keys.end())
172  // Key doesn't exist, can't remove
173  return false;
174 
175  bool found = false;
176 
177  // Find and remove the key's line
178  LineList::iterator line;
179  for (line = _lines.begin(); line != _lines.end(); ++line) {
180  if (line->key == k) {
181  _lines.erase(line);
182  found = true;
183  break;
184  }
185  }
186 
187  // If we couldn't find the key in the list, something is *really* broken
188  assert(found == true);
189 
190  // Remove the key
191  _keys.erase(k);
192  return true;
193 }
194 
195 bool ConfigDomain::renameKey(const UString &oldName, const UString &newName) {
196  StringIMap::iterator k = _keys.find(oldName);
197  if (k == _keys.end())
198  // Old name doesn't exist
199  return false;
200 
201  if (_keys.find(newName) != _keys.end())
202  // New name already exists
203  return false;
204 
205  // Find that key in the line list
206  LineList::iterator line;
207  for (line = _lines.begin(); line != _lines.end(); ++line)
208  if (line->key == k)
209  break;
210 
211  // If that happens, something is *really* broken
212  assert(line != _lines.end());
213 
214  // Get the value
215  UString value = k->second;
216 
217  // Remove from the map
218  _keys.erase(k);
219 
220  // Insert under the name name
221  std::pair<StringIMap::iterator, bool> result =
222  _keys.insert(std::make_pair(newName, value));
223 
224  // Update the iterator in the line
225  line->key = result.first;
226 
227  return true;
228 }
229 
230 void ConfigDomain::set(const ConfigDomain &domain, bool clobber) {
231  for (LineList::const_iterator l = domain._lines.begin(); l != domain._lines.end(); ++l) {
232  if (l->key == domain._keys.end())
233  // Comment-only line, ignore
234  continue;
235 
236  StringIMap::iterator k = _keys.find(l->key->first);
237  if (k == _keys.end()) {
238  // Key doesn't yet exist in the target domain, add it
239 
240  std::pair<StringIMap::iterator, bool> result =
241  _keys.insert(std::make_pair(l->key->first, l->key->second));
242 
243  _lines.push_back(Line());
244  _lines.back().key = result.first;
245 
246  } else {
247  // Key already exists in the target domain, only overwrite when told to
248  if (!clobber)
249  continue;
250 
251  k->second = l->key->second;
252  }
253  }
254 }
255 
256 
258 }
259 
261 }
262 
263 bool ConfigFile::isValidName(const UString &name) {
264  for (UString::iterator it = name.begin(); it != name.end(); ++it) {
265  uint32 c = *it;
266 
267  if (UString::isASCII(c) &&
268  (!isalnum(c) && (c != '-') && (c != '_') && (c != '.') && (c != ' ')))
269  return false;
270  }
271 
272  return true;
273 }
274 
276  _domainList.clear();
277  _domainMap.clear();
278 
279  _prologue.clear();
280  _epilogue.clear();
281 }
282 
284  UString comment;
285 
287 
288  int lineNumber = 0;
289  int domainLineNumber = 0;
290  while (!stream.eos()) {
291  lineNumber++;
292 
293  // Read a line
294  UString line = readStringLine(stream, kEncodingUTF8);
295 
296  // Parse it
297  UString domainName;
298  UString key, value, lineComment;
299  parseConfigLine(line, domainName, key, value, lineComment, lineNumber);
300 
301  if (!domainName.empty()) {
302  // New domain
303 
304  // Finish up the old domain
305  addDomain(domain.get(), domainLineNumber);
306  domain.release();
307 
308  // Check that the name is actually valid
309  if (!isValidName(domainName))
310  throw Exception("\"%s\" isn't a valid domain name (line %d)",
311  domainName.c_str(), lineNumber);
312 
313  // Create the new domain
314  domain.reset(new ConfigDomain(domainName));
315 
316  domain->_prologue = comment;
317  domain->_comment = lineComment;
318 
319  comment.clear();
320  lineComment.clear();
321 
322  domainLineNumber = lineNumber;
323  }
324 
325  if (!key.empty()) {
326  // New key
327 
328  if (!domain)
329  throw Exception("Found a key outside a domain (line %d)", lineNumber);
330 
331  if (!isValidName(key))
332  throw Exception("\"%s\" isn't a valid key name (line %d)",
333  key.c_str(), lineNumber);
334 
335  // Add collected comments to the domain
336  if (!comment.empty())
337  addDomainKey(*domain, "", "", comment, lineNumber);
338 
339  // Add the key to the domain
340  addDomainKey(*domain, key, value, lineComment, lineNumber);
341 
342  comment.clear();
343  lineComment.clear();
344  }
345 
346  // Collect comments, we don't yet know where those belong to.
347  if (!lineComment.empty()) {
348  if (!comment.empty())
349  comment += '\n';
350  comment += lineComment;
351  }
352 
353  // Empty line, associate collected comments with the current domain
354  if (domainName.empty() && key.empty() && value.empty() && lineComment.empty()) {
355  if (!comment.empty() && !stream.eos()) {
356 
357  if (!domain) {
358  // We have no domain yet, add it to the file's prologue
359  if (!_prologue.empty())
360  _prologue += '\n';
361  _prologue += comment;
362  } else
363  addDomainKey(*domain, "", "", comment, lineNumber);
364 
365  comment.clear();
366  }
367  }
368 
369  }
370 
371  // Finish up the last domain
372  addDomain(domain.get(), domainLineNumber);
373  domain.release();
374 
375  // We still have comments, those apparently belong to the bottom of the file
376  if (!comment.empty())
377  _epilogue = comment;
378 }
379 
381  const UString &value, const UString &comment, int lineNumber) {
382 
383  // Create new line
384  domain._lines.push_back(ConfigDomain::Line());
385  ConfigDomain::Line &line = domain._lines.back();
386 
387  // Add comment
388  line.comment = comment;
389 
390  if (!key.empty()) {
391  // We have a key/value pair, add it to the map
392  std::pair<StringIMap::iterator, bool> result =
393  domain._keys.insert(std::make_pair(key, value));
394 
395  if (!result.second)
396  // Wasn't inserted, so a key of the name was already in there
397  throw Exception("Duplicate key \"%s\" in domain \"%s\" (line %d)",
398  key.c_str(), domain._name.c_str(), lineNumber);
399 
400  // And set the iterator in the line accordingly
401  line.key = result.first;
402  } else
403  // No key, reflect that in the iterator
404  line.key = domain._keys.end();
405 }
406 
407 void ConfigFile::addDomain(ConfigDomain *domain, int lineNumber) {
408  if (!domain)
409  // No domain, nothing to do
410  return;
411 
412  // Sanity check
413  if (hasDomain(domain->_name))
414  throw Exception("Duplicate domain \"%s\" (line %d)", domain->_name.c_str(), lineNumber);
415 
416  // Add the domain to the list and map
417  _domainList.push_back(domain);
418  _domainMap.insert(std::make_pair(domain->_name, domain));
419 }
420 
421 void ConfigFile::parseConfigLine(const UString &line, UString &domainName,
422  UString &key, UString &value, UString &comment, int lineNumber) {
423 
424  bool hasComment = false;
425 
426  int state = 0;
427  for (UString::iterator l = line.begin(); l != line.end(); ++l) {
428  uint32 c = *l;
429 
430  if (state == 1) {
431  // Collecting comments
432 
433  comment += c;
434  continue;
435 
436  } else if (state == 2) {
437  // Collecting domain name
438 
439  if (c != ']') {
440  domainName += c;
441  } else
442  state = 0;
443 
444  continue;
445  }
446 
447  if ((c == '#') || ((c == ';') && (l == line.begin()))) {
448  // Found a comment
449  state = 1;
450  hasComment = true;
451  } else if (c == '[') {
452  // Found the start of a domain name
453  state = 2;
454  } else if (c == ']') {
455  // Parse error
456  throw Exception("Found extra ']' (line %d)", lineNumber);
457  } else if (c == '=') {
458  // Found the startt of a value
459  state = 3;
460  } else {
461  // Collect either a key or a value
462  if (state == 0)
463  key += c;
464  else if (state == 3)
465  value += c;
466  }
467  }
468 
469  // Sanity check
470  if (state == 2)
471  throw Exception("Missing ']' (line %d)", lineNumber);
472 
473  // Remove excess whitespace
474  key.trim();
475  value.trim();
476  comment.trim();
477 
478  // Read the comment character #
479  if (hasComment)
480  comment = "# " + comment;
481 
482  // Sanity checks
483  if (!domainName.empty())
484  if (!key.empty() || !value.empty())
485  throw Exception("Parse error (line %d)",
486  lineNumber);
487 
488  if (key.empty() && !value.empty())
489  throw Exception("Value with a key (line %d)", lineNumber);
490 }
491 
492 void ConfigFile::save(WriteStream &stream) const {
493  // Write file prologue
494  if (!_prologue.empty()) {
495  stream.writeString(_prologue);
496  stream.writeByte('\n');
497  stream.writeByte('\n');
498  }
499 
500  // Domains
501  for (DomainList::const_iterator domain = _domainList.begin(); domain != _domainList.end(); ++domain) {
502  // Write domain prologue
503  if (!(*domain)->_prologue.empty()) {
504  stream.writeString((*domain)->_prologue);
505  stream.writeByte('\n');
506  }
507 
508  // Write domain name
509  stream.writeByte('[');
510  stream.writeString((*domain)->_name);
511  stream.writeByte(']');
512 
513  // Write domain comment
514  if (!(*domain)->_comment.empty()) {
515  stream.writeByte(' ');
516  stream.writeString((*domain)->_comment);
517  }
518 
519  stream.writeByte('\n');
520 
521  // Lines
522  for (ConfigDomain::LineList::const_iterator line = (*domain)->_lines.begin(); line != (*domain)->_lines.end(); ++line) {
523  // Write key
524  if (line->key != (*domain)->_keys.end()) {
525  stream.writeString(line->key->first);
526  stream.writeByte('=');
527  stream.writeString(line->key->second);
528  if (!line->comment.empty())
529  stream.writeByte(' ');
530  }
531 
532  // Write comment
533  if (!line->comment.empty())
534  stream.writeString(line->comment);
535 
536  stream.writeByte('\n');
537  }
538 
539  stream.writeByte('\n');
540  }
541 
542  // Write the epilogue
543  if (!_epilogue.empty()) {
544  stream.writeString(_epilogue);
545  stream.writeByte('\n');
546  }
547 
548  stream.flush();
549 }
550 
551 bool ConfigFile::hasDomain(const UString &name) const {
552  return _domainMap.find(name) != _domainMap.end();
553 }
554 
556  return _domainList;
557 }
558 
560  DomainMap::iterator domain = _domainMap.find(name);
561  if (domain != _domainMap.end())
562  return domain->second;
563 
564  return 0;
565 }
566 
567 const ConfigDomain *ConfigFile::getDomain(const UString &name) const {
568  DomainMap::const_iterator domain = _domainMap.find(name);
569  if (domain != _domainMap.end())
570  return domain->second;
571 
572  return 0;
573 }
574 
576  ConfigDomain *domain = getDomain(name);
577  if (domain)
578  // A domain with this name already exists, return that one then
579  return domain;
580 
581  // Create a new domain
582  domain = new ConfigDomain(name);
583 
584  _domainList.push_back(domain);
585  _domainMap.insert(std::make_pair(name, domain));
586 
587  return domain;
588 }
589 
591  DomainMap::iterator domain = _domainMap.find(name);
592  if (domain == _domainMap.end())
593  // Domain doesn't exist, can't remove
594  return false;
595 
596  _domainList.remove(domain->second);
597 
598  // Remove the domain
599  _domainMap.erase(domain);
600  return true;
601 }
602 
603 bool ConfigFile::renameDomain(const UString &oldName, const UString &newName) {
604  DomainMap::iterator domain = _domainMap.find(oldName);
605  if (domain == _domainMap.end())
606  // Old name doesn't exist
607  return false;
608 
609  if (_domainMap.find(newName) != _domainMap.end())
610  // New name already exists
611  return false;
612 
613  // Get the domain pointer
614  ConfigDomain *d = domain->second;
615 
616  // Remove from the map
617  _domainMap.erase(domain);
618 
619  // Insert into the map under the new name
620  d->_name = newName;
621  _domainMap.insert(std::make_pair(newName, d));
622 
623  return true;
624 }
625 
626 } // End of namespace Common
bool removeDomain(const UString &name)
Definition: configfile.cpp:590
void setDouble(const UString &key, double value)
Definition: configfile.cpp:165
bool renameDomain(const UString &oldName, const UString &newName)
Definition: configfile.cpp:603
Definition: 2dafile.h:39
virtual void flush()
Commit any buffered data to the underlying channel or storage medium; unbuffered streams can use the ...
Definition: writestream.cpp:69
void save(WriteStream &stream) const
Definition: configfile.cpp:492
A class holding an UTF-8 string.
Definition: ustring.h:48
void writeString(const UString &str)
Write the given string to the stream, encoded as UTF-8.
Definition: writestream.cpp:94
void reset(PointerType o=0)
Resets the pointer with the new value.
Definition: scopedptr.h:87
PointerType release()
Returns the plain pointer value and releases ScopedPtr.
Definition: scopedptr.h:103
UString composeString(T value)
Convert any POD integer, float/double or bool type into a string.
Definition: strutil.cpp:276
virtual bool eos() const =0
Returns true if a read failed because the stream has been reached.
void setString(const UString &key, const UString &value)
Definition: configfile.cpp:149
void load(SeekableReadStream &stream)
Definition: configfile.cpp:283
iterator begin() const
Definition: ustring.cpp:253
DomainList _domainList
List of domains in order.
Definition: configfile.h:149
void parseConfigLine(const UString &line, UString &domainName, UString &key, UString &value, UString &comment, int lineNumber)
Definition: configfile.cpp:421
UString getString(const UString &key, const UString &def="") const
Definition: configfile.cpp:63
ConfigDomain(const UString &name)
Definition: configfile.cpp:40
Utility templates and functions for working with strings and streams.
const DomainList & getDomains() const
Definition: configfile.cpp:555
StringIMap _keys
The key/value pairs of the config domain.
Definition: configfile.h:96
A simple scoped smart pointer template.
void exceptionDispatcherWarning(const char *s,...)
Exception dispatcher that prints the exception as a warning, and adds another reason on top...
Definition: error.cpp:158
Basic exceptions to throw.
UString readStringLine(SeekableReadStream &stream, Encoding encoding)
Read a line with the given encoding out of a stream.
Definition: encoding.cpp:310
utf8::iterator< std::string::const_iterator > iterator
Definition: ustring.h:50
const char * c_str() const
Return the (utf8 encoded) string data.
Definition: ustring.cpp:249
bool removeKey(const UString &key)
Definition: configfile.cpp:169
int getInt(const UString &key, int def=0) const
Definition: configfile.cpp:86
bool getKey(const UString &key, UString &value) const
Definition: configfile.cpp:54
A class storing a basic configuration file.
DomainMap _domainMap
Domains indexed by name.
Definition: configfile.h:150
LineList _lines
The lines of the config domain.
Definition: configfile.h:95
Basic writing stream interfaces.
Utility functions for working with differing string encodings.
bool empty() const
Is the string empty?
Definition: ustring.cpp:245
StackException Exception
Definition: error.h:59
A scoped plain pointer, allowing pointer-y access and normal deletion.
Definition: scopedptr.h:120
void writeByte(byte value)
Definition: writestream.h:88
bool renameKey(const UString &oldName, const UString &newName)
Definition: configfile.cpp:195
Basic reading stream interfaces.
Generic interface for a writable data stream.
Definition: writestream.h:64
void setBool(const UString &key, bool value)
Definition: configfile.cpp:153
void setInt(const UString &key, int value)
Definition: configfile.cpp:157
double getDouble(const UString &key, double def=0.0) const
Definition: configfile.cpp:116
PointerType get() const
Returns the plain pointer value.
Definition: scopedptr.h:96
UString _prologue
Comments on top of the file.
Definition: configfile.h:152
void remove(const typename std::list< T *>::value_type &val)
Definition: ptrlist.h:94
static bool isASCII(uint32 c)
Is the character an ASCII character?
Definition: ustring.cpp:785
void clear()
Definition: ptrlist.h:47
const UString & getName() const
Definition: configfile.cpp:46
Accessor for a domain (section) in a config file.
Definition: configfile.h:46
uint32_t uint32
Definition: types.h:204
void set(const ConfigDomain &domain, bool clobber=true)
Add the keys of another domain.
Definition: configfile.cpp:230
bool hasDomain(const UString &name) const
Definition: configfile.cpp:551
bool getBool(const UString &key, bool def=false) const
Definition: configfile.cpp:71
A line in the config domain.
Definition: configfile.h:86
void clear()
Reset everything stored in this config file.
Definition: configfile.cpp:275
UString _epilogue
Comments at the bottom of the file.
Definition: configfile.h:153
void addDomainKey(ConfigDomain &domain, const UString &key, const UString &value, const UString &comment, int lineNumber)
Definition: configfile.cpp:380
ConfigDomain * getDomain(const UString &name)
Definition: configfile.cpp:559
iterator end() const
Definition: ustring.cpp:257
void setKey(const UString &key, const UString &value)
Definition: configfile.cpp:131
void clear()
Clear the string&#39;s contents.
Definition: ustring.cpp:236
uint getUint(const UString &key, uint def=0) const
Definition: configfile.cpp:101
static bool isValidName(const UString &name)
Check whether the given string is a valid section or key name.
Definition: configfile.cpp:263
bool hasKey(const UString &key) const
Definition: configfile.cpp:50
Interface for a seekable & readable data stream.
Definition: readstream.h:265
void parseString(const UString &str, T &value, bool allowEmpty)
Parse a string into any POD integer, float/double or bool type.
Definition: strutil.cpp:215
UString comment
Line comment.
Definition: configfile.h:88
ConfigDomain * addDomain(const UString &name)
Definition: configfile.cpp:575
unsigned int uint
Definition: types.h:211
StringIMap::const_iterator key
Pointer to the key/value pair.
Definition: configfile.h:87
void setUint(const UString &key, uint value)
Definition: configfile.cpp:161