The Battle for Wesnoth  1.19.1+dev
parser.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2024
3  by Philippe Plantier <ayin@anathas.org>
4  Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
5  Copyright (C) 2003 by David White <dave@whitevine.net>
6  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
7 
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY.
14 
15  See the COPYING file for more details.
16 */
17 
18 /**
19  * @file
20  * Read/Write & analyze WML- and config-files.
21  */
22 
23 #include "serialization/parser.hpp"
24 
25 #include "config.hpp"
26 #include "gettext.hpp"
27 #include "log.hpp"
32 #include "wesconfig.h"
33 
34 #include <boost/algorithm/string/replace.hpp>
35 #include <boost/iostreams/filter/bzip2.hpp>
36 #include <boost/iostreams/filtering_stream.hpp>
37 
38 #if defined(_MSC_VER)
39 #pragma warning(push)
40 #pragma warning(disable : 4456)
41 #pragma warning(disable : 4458)
42 #endif
43 
44 #include <boost/iostreams/filter/gzip.hpp>
45 
46 #if defined(_MSC_VER)
47 #pragma warning(pop)
48 #endif
49 
50 #include <stack>
51 
52 static lg::log_domain log_config("config");
53 #define ERR_CF LOG_STREAM(err, log_config)
54 #define WRN_CF LOG_STREAM(warn, log_config)
55 #define LOG_CF LOG_STREAM(info, log_config)
56 
57 static const std::size_t max_recursion_levels = 1000;
58 
59 namespace
60 {
61 // ==================================================================================
62 // PARSER
63 // ==================================================================================
64 
65 class parser
66 {
67  parser() = delete;
68 
69  parser(const parser&) = delete;
70  parser& operator=(const parser&) = delete;
71 
72 public:
73  parser(config& cfg, std::istream& in, abstract_validator* validator = nullptr)
74  : cfg_(cfg)
75  , tok_(in)
76  , validator_(validator)
77  , elements()
78  {
79  }
80 
81  ~parser()
82  {
83  }
84 
85  void operator()();
86 
87 private:
88  void parse_element();
89  void parse_variable();
90 
91  std::string lineno_string(utils::string_map& map,
92  const std::string& lineno,
93  const std::string& error_string,
94  const std::string& hint_string = "",
95  const std::string& debug_string = "");
96 
97  void error(const std::string& message, const std::string& pos_format = "");
98 
99  struct element
100  {
101  element(config* cfg, const std::string& name, int start_line = 0, const std::string& file = "")
102  : cfg(cfg)
103  , name(name)
104  , start_line(start_line)
105  , file(file)
106  {
107  }
108 
109  config* cfg;
110  std::string name;
111  int start_line;
112  std::string file;
113  };
114 
115  config& cfg_;
116  tokenizer tok_;
117  abstract_validator* validator_;
118 
119  std::stack<element> elements;
120 };
121 
122 void parser::operator()()
123 {
124  cfg_.clear();
125  elements.emplace(&cfg_, "");
126 
127  if(validator_) {
128  validator_->open_tag("", cfg_, tok_.get_start_line(), tok_.get_file());
129  }
130 
131  do {
132  tok_.next_token();
133 
134  switch(tok_.current_token().type) {
135  case token::LF:
136  continue;
137 
138  case '[':
139  parse_element();
140  break;
141 
142  case token::STRING:
143  parse_variable();
144  break;
145 
146  default:
147  if(static_cast<unsigned char>(tok_.current_token().value[0]) == 0xEF &&
148  static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBB &&
149  static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBF
150  ) {
151  utils::string_map i18n_symbols;
152  std::stringstream ss;
153  ss << tok_.get_start_line() << " " << tok_.get_file();
154  ERR_CF << lineno_string(i18n_symbols, ss.str(), "Skipping over a utf8 BOM at $pos");
155  } else {
156  error(_("Unexpected characters at line start"));
157  }
158 
159  break;
160 
161  case token::END:
162  break;
163  }
164  } while(tok_.current_token().type != token::END);
165 
166  // The main element should be there. If it is not, this is a parser error.
167  assert(!elements.empty());
168 
169  if(validator_) {
170  element& el = elements.top();
171  validator_->validate(*el.cfg, el.name, el.start_line, el.file);
172  validator_->close_tag();
173  }
174 
175  if(elements.size() != 1) {
176  utils::string_map i18n_symbols;
177  i18n_symbols["tag"] = elements.top().name;
178 
179  std::stringstream ss;
180  ss << elements.top().start_line << " " << elements.top().file;
181 
182  error(lineno_string(i18n_symbols, ss.str(),
183  _("Missing closing tag for tag [$tag]"),
184  _("expected at $pos")),
185  _("opened at $pos")
186  );
187  }
188 }
189 
190 void parser::parse_element()
191 {
192  tok_.next_token();
193 
194  std::string elname;
195  config* current_element = nullptr;
196  config* parent = nullptr;
197 
198  switch(tok_.current_token().type) {
199  case token::STRING: // [element]
200  elname = tok_.current_token().value;
201 
202  if(tok_.next_token().type != ']') {
203  error(_("Unterminated [element] tag"));
204  }
205 
206  // Add the element
207  parent = elements.top().cfg;
208  current_element = &(parent->add_child(elname));
209  elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
210 
211  if(validator_) {
212  validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
213  }
214 
215  break;
216 
217  case '+': // [+element]
218  if(tok_.next_token().type != token::STRING) {
219  error(_("Invalid tag name"));
220  }
221 
222  elname = tok_.current_token().value;
223 
224  if(tok_.next_token().type != ']') {
225  error(_("Unterminated [+element] tag"));
226  }
227 
228  // Find the last child of the current element whose name is element
229  parent = elements.top().cfg;
230  if(auto c = parent->optional_child(elname, -1)) {
231  current_element = c.ptr();
232 
233  if(validator_) {
234  validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file(), true);
235  }
236  } else {
237  current_element = &parent->add_child(elname);
238 
239  if(validator_) {
240  validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
241  }
242  }
243 
244  elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
245  break;
246 
247  case '/': // [/element]
248  if(tok_.next_token().type != token::STRING) {
249  error(_("Invalid closing tag name"));
250  }
251 
252  elname = tok_.current_token().value;
253 
254  if(tok_.next_token().type != ']') {
255  error(_("Unterminated closing tag"));
256  }
257 
258  if(elements.size() <= 1) {
259  error(_("Unexpected closing tag"));
260  }
261 
262  if(elname != elements.top().name) {
263  utils::string_map i18n_symbols;
264  i18n_symbols["tag1"] = elements.top().name;
265  i18n_symbols["tag2"] = elname;
266 
267  std::stringstream ss;
268  ss << elements.top().start_line << " " << elements.top().file;
269 
270  error(lineno_string(i18n_symbols, ss.str(),
271  _("Found invalid closing tag [/$tag2] for tag [$tag1]"),
272  _("opened at $pos")),
273  _("closed at $pos")
274  );
275  }
276 
277  if(validator_) {
278  element& el = elements.top();
279  validator_->validate(*el.cfg, el.name, el.start_line, el.file);
280  validator_->close_tag();
281  }
282 
283  elements.pop();
284  break;
285 
286  default:
287  error(_("Invalid tag name"));
288  }
289 }
290 
291 void parser::parse_variable()
292 {
293  config& cfg = *elements.top().cfg;
294  std::vector<std::string> variables;
295  variables.emplace_back();
296 
297  while(tok_.current_token().type != '=') {
298  switch(tok_.current_token().type) {
299  case token::STRING:
300  if(!variables.back().empty()) {
301  variables.back() += ' ';
302  }
303 
304  variables.back() += tok_.current_token().value;
305  break;
306 
307  case ',':
308  if(variables.back().empty()) {
309  error(_("Empty variable name"));
310  } else {
311  variables.emplace_back();
312  }
313 
314  break;
315 
316  default:
317  error(_("Unexpected characters after variable name (expected , or =)"));
318  break;
319  }
320 
321  tok_.next_token();
322  }
323 
324  if(variables.back().empty()) {
325  error(_("Empty variable name"));
326  }
327 
328  t_string_base buffer;
329 
330  std::vector<std::string>::const_iterator curvar = variables.begin();
331 
332  bool ignore_next_newlines = false, previous_string = false;
333 
334  while(true) {
335  tok_.next_token();
336  assert(curvar != variables.end());
337 
338  switch(tok_.current_token().type) {
339  case ',':
340  if((curvar + 1) != variables.end()) {
341  if(buffer.translatable()) {
342  cfg[*curvar] = t_string(buffer);
343  } else {
344  cfg[*curvar] = buffer.value();
345  }
346 
347  if(validator_) {
348  validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
349  }
350 
351  buffer = t_string_base();
352  ++curvar;
353  } else {
354  buffer += ",";
355  }
356 
357  break;
358 
359  case '_':
360  tok_.next_token();
361 
362  switch(tok_.current_token().type) {
364  error(_("Unterminated quoted string"));
365  break;
366 
367  case token::QSTRING:
368  buffer += t_string_base(tok_.current_token().value, tok_.textdomain());
369  break;
370 
371  default:
372  buffer += "_";
373  buffer += tok_.current_token().value;
374  break;
375 
376  case token::END:
377  case token::LF:
378  buffer += "_";
379  goto finish;
380  }
381 
382  break;
383 
384  case '+':
385  ignore_next_newlines = true;
386  continue;
387 
388  case token::STRING:
389  if(previous_string) {
390  buffer += " ";
391  }
392 
393  [[fallthrough]];
394 
395  default:
396  buffer += tok_.current_token().value;
397  break;
398 
399  case token::QSTRING:
400  buffer += tok_.current_token().value;
401  break;
402 
404  error(_("Unterminated quoted string"));
405  break;
406 
407  case token::LF:
408  if(ignore_next_newlines) {
409  continue;
410  }
411 
412  [[fallthrough]];
413 
414  case token::END:
415  goto finish;
416  }
417 
418  previous_string = tok_.current_token().type == token::STRING;
419  ignore_next_newlines = false;
420  }
421 
422 finish:
423 
424  if(buffer.translatable()) {
425  cfg[*curvar] = t_string(buffer);
426  } else {
427  cfg[*curvar] = buffer.value();
428  }
429 
430  if(validator_) {
431  validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
432  }
433 
434  while(++curvar != variables.end()) {
435  cfg[*curvar] = "";
436  }
437 }
438 
439 /**
440  * This function is crap. Don't use it on a string_map with prefixes.
441  */
442 std::string parser::lineno_string(utils::string_map& i18n_symbols,
443  const std::string& lineno,
444  const std::string& error_string,
445  const std::string& hint_string,
446  const std::string& debug_string)
447 {
448  i18n_symbols["pos"] = ::lineno_string(lineno);
449  std::string result = error_string;
450 
451  if(!hint_string.empty()) {
452  result += '\n' + hint_string;
453  }
454 
455  if(!debug_string.empty()) {
456  result += '\n' + debug_string;
457  }
458 
459  for(utils::string_map::value_type& var : i18n_symbols) {
460  boost::algorithm::replace_all(result, std::string("$") + var.first, std::string(var.second));
461  }
462 
463  return result;
464 }
465 
466 void parser::error(const std::string& error_type, const std::string& pos_format)
467 {
468  std::string hint_string = pos_format;
469 
470  if(hint_string.empty()) {
471  hint_string = _("at $pos");
472  }
473 
474  utils::string_map i18n_symbols;
475  i18n_symbols["error"] = error_type;
476 
477  std::stringstream ss;
478  ss << tok_.get_start_line() << " " << tok_.get_file();
479 
480 #ifdef DEBUG_TOKENIZER
481  i18n_symbols["value"] = tok_.current_token().value;
482  i18n_symbols["previous_value"] = tok_.previous_token().value;
483 
484  const std::string& tok_state = _("Value: '$value' Previous: '$previous_value'");
485 #else
486  const std::string& tok_state = "";
487 #endif
488 
489  const std::string& message = lineno_string(i18n_symbols, ss.str(), "$error", hint_string, tok_state);
490 
491  throw config::error(message);
492 }
493 
494 
495 // ==================================================================================
496 // HELPERS FOR WRITE_KEY_VAL
497 // ==================================================================================
498 
499 /**
500  * Copies a string fragment and converts it to a suitable format for WML.
501  * (I.e., quotes are doubled.)
502  */
503 std::string escaped_string(const std::string::const_iterator& begin, const std::string::const_iterator& end)
504 {
505  std::string res;
506  std::string::const_iterator iter = begin;
507 
508  while(iter != end) {
509  const char c = *iter;
510  res.append(c == '"' ? 2 : 1, c);
511  ++iter;
512  }
513 
514  return res;
515 }
516 
517 /**
518  * Copies a string and converts it to a suitable format for WML.
519  * (I.e., quotes are doubled.)
520  */
521 inline std::string escaped_string(const std::string& value)
522 {
523  return escaped_string(value.begin(), value.end());
524 }
525 
526 class write_key_val_visitor
527 {
528 public:
529  write_key_val_visitor(std::ostream& out, unsigned level, std::string& textdomain, const std::string& key)
530  : out_(out)
531  , level_(level)
532  , textdomain_(textdomain)
533  , key_(key)
534  {
535  }
536 
537  // Generic visitor just streams "key=value".
538  template<typename T>
539  void operator()(const T& v) const
540  {
541  indent();
542  out_ << key_ << '=' << v << '\n';
543  }
544 
545  //
546  // Specialized visitors for things that go in quotes:
547  //
548 
549  void operator()(const std::monostate&) const
550  {
551  // Treat blank values as nonexistent which fits better than treating them as empty strings.
552  }
553 
554  void operator()(const std::string& s) const
555  {
556  indent();
557  out_ << key_ << '=' << '"' << escaped_string(s) << '"' << '\n';
558  }
559 
560  void operator()(const t_string& s) const;
561 
562 private:
563  void indent() const
564  {
565  for(unsigned i = 0; i < level_; ++i) {
566  out_ << '\t';
567  }
568  }
569 
570  std::ostream& out_;
571  const unsigned level_;
572  std::string& textdomain_;
573  const std::string& key_;
574 };
575 
576 /**
577  * Writes all the parts of a translatable string.
578  *
579  * @note If the first part is translatable and in the wrong textdomain,
580  * the textdomain change has to happen before the attribute name.
581  * That is the reason for not outputting the key beforehand and
582  * letting this function do it.
583  */
584 void write_key_val_visitor::operator()(const t_string& value) const
585 {
586  bool first = true;
587 
588  for(t_string::walker w(value); !w.eos(); w.next()) {
589  if(!first) {
590  out_ << " +\n";
591  }
592 
593  if(w.translatable() && w.textdomain() != textdomain_) {
594  textdomain_ = w.textdomain();
595  out_ << "#textdomain " << textdomain_ << '\n';
596  }
597 
598  indent();
599 
600  if(first) {
601  out_ << key_ << '=';
602  } else {
603  out_ << '\t';
604  }
605 
606  if(w.translatable()) {
607  out_ << '_';
608  }
609 
610  out_ << '"' << escaped_string(w.begin(), w.end()) << '"';
611  first = false;
612  }
613 
614  out_ << '\n';
615 }
616 
617 } // end anon namespace
618 
619 
620 // ==================================================================================
621 // PUBLIC FUNCTION IMPLEMENTATIONS
622 // ==================================================================================
623 
624 void read(config& cfg, std::istream& in, abstract_validator* validator)
625 {
626  parser(cfg, in, validator)();
627 }
628 
629 void read(config& cfg, const std::string& in, abstract_validator* validator)
630 {
631  std::istringstream ss(in);
632  parser(cfg, ss, validator)();
633 }
634 
635 template<typename decompressor>
636 void read_compressed(config& cfg, std::istream& file, abstract_validator* validator)
637 {
638  // An empty gzip file seems to confuse boost on MSVC, so return early if this is the case.
639  if(file.peek() == EOF) {
640  return;
641  }
642 
643  boost::iostreams::filtering_stream<boost::iostreams::input> filter;
644  filter.push(decompressor());
645  filter.push(file);
646 
647  /* This causes gzip_error (and the corresponding bz2 error, std::ios_base::failure) to be
648  * thrown here. save_index_class::data expects that and config_cache::read_cache and other
649  * functions are also capable of catching.
650  *
651  * Note that parser(cuff, filter,validator)(); -> tokenizer::tokenizer can throw exceptions
652  * too (meaning this function already threw these exceptions before this patch).
653  *
654  * We try to fix https://svn.boost.org/trac/boost/ticket/5237 by not creating empty gz files.
655  */
656  filter.exceptions(filter.exceptions() | std::ios_base::badbit);
657 
658  /*
659  * It sometimes seems the file is not empty but still has no real data.
660  * Filter that case here. The previous test might be no longer required but keep it for now.
661  *
662  * On msvc filter.peek() != EOF does not imply filter.good().
663  * We never create empty compressed gzip files because boosts gzip fails at doing that, but
664  * empty compressed bz2 files are possible.
665  */
666  if(filter.peek() == EOF) {
667  LOG_CF << "Empty compressed file or error at reading a compressed file.";
668  return;
669  }
670 
671  if(!filter.good()) {
672  LOG_CF << " filter.peek() != EOF but !filter.good()."
673  << "This indicates a malformed gz stream and can make Wesnoth crash.";
674  }
675 
676  parser(cfg, filter, validator)();
677 }
678 
679 /** Might throw a std::ios_base::failure especially a gzip_error. */
680 void read_gz(config& cfg, std::istream& file, abstract_validator* validator)
681 {
682  read_compressed<boost::iostreams::gzip_decompressor>(cfg, file, validator);
683 }
684 
685 /** Might throw a std::ios_base::failure especially bzip2_error. */
686 void read_bz2(config& cfg, std::istream& file, abstract_validator* validator)
687 {
688  read_compressed<boost::iostreams::bzip2_decompressor>(cfg, file, validator);
689 }
690 
691 void write_key_val(std::ostream& out,
692  const std::string& key,
693  const config::attribute_value& value,
694  unsigned level,
695  std::string& textdomain)
696 {
697  value.apply_visitor(write_key_val_visitor(out, level, textdomain, key));
698 }
699 
700 void write_open_child(std::ostream& out, const std::string& child, unsigned int level)
701 {
702  out << std::string(level, '\t') << '[' << child << "]\n";
703 }
704 
705 void write_close_child(std::ostream& out, const std::string& child, unsigned int level)
706 {
707  out << std::string(level, '\t') << "[/" << child << "]\n";
708 }
709 
710 static void write_internal(const config& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0)
711 {
712  if(tab > max_recursion_levels) {
713  throw config::error("Too many recursion levels in config write");
714  }
715 
716  for(const config::attribute& i : cfg.attribute_range()) {
717  if(!config::valid_attribute(i.first)) {
718  ERR_CF << "Config contains invalid attribute name '" << i.first << "', skipping...";
719  continue;
720  }
721 
722  write_key_val(out, i.first, i.second, tab, textdomain);
723  }
724 
725  for(const config::any_child item : cfg.all_children_range()) {
726  if(!config::valid_tag(item.key)) {
727  ERR_CF << "Config contains invalid tag name '" << item.key << "', skipping...";
728  continue;
729  }
730 
731  write_open_child(out, item.key, tab);
732  write_internal(item.cfg, out, textdomain, tab + 1);
733  write_close_child(out, item.key, tab);
734  }
735 }
736 
737 static void write_internal(const configr_of& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0)
738 {
739  if(tab > max_recursion_levels) {
740  throw config::error("Too many recursion levels in config write");
741  }
742 
743  if(cfg.data_) {
744  write_internal(*cfg.data_, out, textdomain, tab);
745  }
746 
747  for(const auto& pair : cfg.subtags_) {
748  assert(pair.first && pair.second);
749 
750  if(!config::valid_tag(*pair.first)) {
751  ERR_CF << "Config contains invalid tag name '" << *pair.first << "', skipping...";
752  continue;
753  }
754 
755  write_open_child(out, *pair.first, tab);
756  write_internal(*pair.second, out, textdomain, tab + 1);
757  write_close_child(out, *pair.first, tab);
758  }
759 }
760 
761 void write(std::ostream& out, const configr_of& cfg, unsigned int level)
762 {
763  std::string textdomain = PACKAGE;
764  write_internal(cfg, out, textdomain, level);
765 }
766 
767 template<typename compressor>
768 void write_compressed(std::ostream& out, const configr_of& cfg)
769 {
770  boost::iostreams::filtering_stream<boost::iostreams::output> filter;
771  filter.push(compressor());
772  filter.push(out);
773 
774  write(filter, cfg);
775 
776  // prevent empty gz files because of https://svn.boost.org/trac/boost/ticket/5237
777  filter << "\n";
778 }
779 
780 void write_gz(std::ostream& out, const configr_of& cfg)
781 {
782  write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
783 }
784 
785 void write_bz2(std::ostream& out, const configr_of& cfg)
786 {
787  write_compressed<boost::iostreams::bzip2_compressor>(out, cfg);
788 }
Used in parsing config file.
Definition: validator.hpp:38
virtual void open_tag(const std::string &name, const config &parent, int start_line, const std::string &file, bool addition=false)=0
Is called when parser opens tag.
Variant for storing WML attributes.
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
const_attr_itors attribute_range() const
Definition: config.cpp:763
static bool valid_tag(config_key_type name)
Definition: config.cpp:127
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:887
static bool valid_attribute(config_key_type name)
Definition: config.cpp:150
attribute_map::value_type attribute
Definition: config.hpp:299
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:385
config & add_child(config_key_type key)
Definition: config.cpp:441
Helper class for translatable strings.
Definition: tstring.hpp:27
bool translatable() const
Definition: tstring.hpp:106
const std::string & value() const
Definition: tstring.hpp:110
Abstract baseclass for the tokenizer.
Definition: tokenizer.hpp:57
std::size_t i
Definition: function.cpp:968
int w
unsigned in
If equal to search_counter, the node is off the list.
static std::string _(const char *str)
Definition: gettext.hpp:93
static int indent
Definition: log.cpp:57
Standard logging facilities (interface).
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
std::map< std::string, t_string > string_map
std::string lineno_string(const std::string &lineno)
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:780
void read_compressed(config &cfg, std::istream &file, abstract_validator *validator)
Definition: parser.cpp:636
void write_compressed(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:768
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:624
static const std::size_t max_recursion_levels
Definition: parser.cpp:57
void read_bz2(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
Definition: parser.cpp:686
void write_close_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:705
static void write_internal(const config &cfg, std::ostream &out, std::string &textdomain, std::size_t tab=0)
Definition: parser.cpp:710
void write_key_val(std::ostream &out, const std::string &key, const config::attribute_value &value, unsigned level, std::string &textdomain)
Definition: parser.cpp:691
void write_bz2(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:785
#define LOG_CF
Definition: parser.cpp:55
void write_open_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:700
#define ERR_CF
Definition: parser.cpp:53
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:761
void read_gz(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:680
static lg::log_domain log_config("config")
std::vector< std::pair< const std::string *, const configr_of * > > subtags_
const config * data_
@ QSTRING
Definition: tokenizer.hpp:36
@ UNTERMINATED_QSTRING
Definition: tokenizer.hpp:37
@ STRING
Definition: tokenizer.hpp:35
mock_char c
static map_location::DIRECTION s
This file contains information about validation abstract level interface.
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define PACKAGE
Definition: wesconfig.h:23