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