The Battle for Wesnoth  1.17.0-dev
parser.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2021
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") << '\n';
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(config& c = parent->child(elname, -1)) {
231  current_element = &c;
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 #ifdef USING_BOOST_VARIANT
528  : public boost::static_visitor<void>
529 #endif
530 {
531 public:
532  write_key_val_visitor(std::ostream& out, unsigned level, std::string& textdomain, const std::string& key)
533  : out_(out)
534  , level_(level)
535  , textdomain_(textdomain)
536  , key_(key)
537  {
538  }
539 
540  // Generic visitor just streams "key=value".
541  template<typename T>
542  void operator()(const T& v) const
543  {
544  indent();
545  out_ << key_ << '=' << v << '\n';
546  }
547 
548  //
549  // Specialized visitors for things that go in quotes:
550  //
551 
552  void operator()(const utils::monostate&) const
553  {
554  // Treat blank values as nonexistent which fits better than treating them as empty strings.
555  }
556 
557  void operator()(const std::string& s) const
558  {
559  indent();
560  out_ << key_ << '=' << '"' << escaped_string(s) << '"' << '\n';
561  }
562 
563  void operator()(const t_string& s) const;
564 
565 private:
566  void indent() const
567  {
568  for(unsigned i = 0; i < level_; ++i) {
569  out_ << '\t';
570  }
571  }
572 
573  std::ostream& out_;
574  const unsigned level_;
575  std::string& textdomain_;
576  const std::string& key_;
577 };
578 
579 /**
580  * Writes all the parts of a translatable string.
581  *
582  * @note If the first part is translatable and in the wrong textdomain,
583  * the textdomain change has to happen before the attribute name.
584  * That is the reason for not outputting the key beforehand and
585  * letting this function do it.
586  */
587 void write_key_val_visitor::operator()(const t_string& value) const
588 {
589  bool first = true;
590 
591  for(t_string::walker w(value); !w.eos(); w.next()) {
592  if(!first) {
593  out_ << " +\n";
594  }
595 
596  if(w.translatable() && w.textdomain() != textdomain_) {
597  textdomain_ = w.textdomain();
598  out_ << "#textdomain " << textdomain_ << '\n';
599  }
600 
601  indent();
602 
603  if(first) {
604  out_ << key_ << '=';
605  } else {
606  out_ << '\t';
607  }
608 
609  if(w.translatable()) {
610  out_ << '_';
611  }
612 
613  out_ << '"' << escaped_string(w.begin(), w.end()) << '"';
614  first = false;
615  }
616 
617  out_ << '\n';
618 }
619 
620 } // end anon namespace
621 
622 
623 // ==================================================================================
624 // PUBLIC FUNCTION IMPLEMENTATIONS
625 // ==================================================================================
626 
627 void read(config& cfg, std::istream& in, abstract_validator* validator)
628 {
629  parser(cfg, in, validator)();
630 }
631 
632 void read(config& cfg, const std::string& in, abstract_validator* validator)
633 {
634  std::istringstream ss(in);
635  parser(cfg, ss, validator)();
636 }
637 
638 template<typename decompressor>
639 void read_compressed(config& cfg, std::istream& file, abstract_validator* validator)
640 {
641  // An empty gzip file seems to confuse boost on MSVC, so return early if this is the case.
642  if(file.peek() == EOF) {
643  return;
644  }
645 
646  boost::iostreams::filtering_stream<boost::iostreams::input> filter;
647  filter.push(decompressor());
648  filter.push(file);
649 
650  /* This causes gzip_error (and the corresponding bz2 error, std::ios_base::failure) to be
651  * thrown here. save_index_class::data expects that and config_cache::read_cache and other
652  * functions are also capable of catching.
653  *
654  * Note that parser(cuff, filter,validator)(); -> tokenizer::tokenizer can throw exceptions
655  * too (meaning this function already threw these exceptions before this patch).
656  *
657  * We try to fix https://svn.boost.org/trac/boost/ticket/5237 by not creating empty gz files.
658  */
659  filter.exceptions(filter.exceptions() | std::ios_base::badbit);
660 
661  /*
662  * It sometimes seems the file is not empty but still has no real data.
663  * Filter that case here. The previous test might be no longer required but keep it for now.
664  *
665  * On msvc filter.peek() != EOF does not imply filter.good().
666  * We never create empty compressed gzip files because boosts gzip fails at doing that, but
667  * empty compressed bz2 files are possible.
668  */
669  if(filter.peek() == EOF) {
670  LOG_CF << "Empty compressed file or error at reading a compressed file.";
671  return;
672  }
673 
674  if(!filter.good()) {
675  LOG_CF << " filter.peek() != EOF but !filter.good()."
676  << "This indicates a malformed gz stream and can make Wesnoth crash.";
677  }
678 
679  parser(cfg, filter, validator)();
680 }
681 
682 /** Might throw a std::ios_base::failure especially a gzip_error. */
683 void read_gz(config& cfg, std::istream& file, abstract_validator* validator)
684 {
685  read_compressed<boost::iostreams::gzip_decompressor>(cfg, file, validator);
686 }
687 
688 /** Might throw a std::ios_base::failure especially bzip2_error. */
689 void read_bz2(config& cfg, std::istream& file, abstract_validator* validator)
690 {
691  read_compressed<boost::iostreams::bzip2_decompressor>(cfg, file, validator);
692 }
693 
694 void write_key_val(std::ostream& out,
695  const std::string& key,
696  const config::attribute_value& value,
697  unsigned level,
698  std::string& textdomain)
699 {
700  value.apply_visitor(write_key_val_visitor(out, level, textdomain, key));
701 }
702 
703 void write_open_child(std::ostream& out, const std::string& child, unsigned int level)
704 {
705  out << std::string(level, '\t') << '[' << child << "]\n";
706 }
707 
708 void write_close_child(std::ostream& out, const std::string& child, unsigned int level)
709 {
710  out << std::string(level, '\t') << "[/" << child << "]\n";
711 }
712 
713 static void write_internal(const config& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0)
714 {
715  if(tab > max_recursion_levels) {
716  throw config::error("Too many recursion levels in config write");
717  }
718 
719  for(const config::attribute& i : cfg.attribute_range()) {
720  if(!config::valid_attribute(i.first)) {
721  ERR_CF << "Config contains invalid attribute name '" << i.first << "', skipping...\n";
722  continue;
723  }
724 
725  write_key_val(out, i.first, i.second, tab, textdomain);
726  }
727 
728  for(const config::any_child item : cfg.all_children_range()) {
729  if(!config::valid_tag(item.key)) {
730  ERR_CF << "Config contains invalid tag name '" << item.key << "', skipping...\n";
731  continue;
732  }
733 
734  write_open_child(out, item.key, tab);
735  write_internal(item.cfg, out, textdomain, tab + 1);
736  write_close_child(out, item.key, tab);
737  }
738 }
739 
740 static void write_internal(const configr_of& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0)
741 {
742  if(tab > max_recursion_levels) {
743  throw config::error("Too many recursion levels in config write");
744  }
745 
746  if(cfg.data_) {
747  write_internal(*cfg.data_, out, textdomain, tab);
748  }
749 
750  for(const auto& pair : cfg.subtags_) {
751  assert(pair.first && pair.second);
752 
753  if(!config::valid_tag(*pair.first)) {
754  ERR_CF << "Config contains invalid tag name '" << *pair.first << "', skipping...\n";
755  continue;
756  }
757 
758  write_open_child(out, *pair.first, tab);
759  write_internal(*pair.second, out, textdomain, tab + 1);
760  write_close_child(out, *pair.first, tab);
761  }
762 }
763 
764 void write(std::ostream& out, const configr_of& cfg, unsigned int level)
765 {
766  std::string textdomain = PACKAGE;
767  write_internal(cfg, out, textdomain, level);
768 }
769 
770 template<typename compressor>
771 void write_compressed(std::ostream& out, const configr_of& cfg)
772 {
773  boost::iostreams::filtering_stream<boost::iostreams::output> filter;
774  filter.push(compressor());
775  filter.push(out);
776 
777  write(filter, cfg);
778 
779  // prevent empty gz files because of https://svn.boost.org/trac/boost/ticket/5237
780  filter << "\n";
781 }
782 
783 void write_gz(std::ostream& out, const configr_of& cfg)
784 {
785  write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
786 }
787 
788 void write_bz2(std::ostream& out, const configr_of& cfg)
789 {
790  write_compressed<boost::iostreams::bzip2_compressor>(out, cfg);
791 }
void write_close_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:708
std::string lineno_string(const std::string &lineno)
static bool valid_tag(config_key_type name)
Definition: config.cpp:183
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:978
std::map< std::string, t_string > string_map
Abstract baseclass for the tokenizer.
Definition: tokenizer.hpp:56
Variant for storing WML attributes.
#define ERR_CF
Definition: parser.cpp:53
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:40
void read_bz2(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
Definition: parser.cpp:689
bool translatable() const
Definition: tstring.hpp:107
attribute_map::value_type attribute
Definition: config.hpp:222
const std::string & value() const
Definition: tstring.hpp:111
static bool valid_attribute(config_key_type name)
Definition: config.cpp:206
static lg::log_domain log_config("config")
This file contains information about validation abstract level interface.
static const std::size_t max_recursion_levels
Definition: parser.cpp:57
static void write_internal(const config &cfg, std::ostream &out, std::string &textdomain, std::size_t tab=0)
Definition: parser.cpp:713
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:683
static std::string _(const char *str)
Definition: gettext.hpp:93
Definitions for the interface to Wesnoth Markup Language (WML).
const_attr_itors attribute_range() const
Definition: config.cpp:858
unsigned in
If equal to search_counter, the node is off the list.
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
Used in parsing config file.
Definition: validator.hpp:37
#define LOG_CF
Definition: parser.cpp:55
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:783
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
void write_compressed(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:771
void write_key_val(std::ostream &out, const std::string &key, const config::attribute_value &value, unsigned level, std::string &textdomain)
Definition: parser.cpp:694
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.
const config * data_
static int indent
Definition: log.cpp:43
#define PACKAGE
Definition: wesconfig.h:23
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
std::size_t i
Definition: function.cpp:967
static map_location::DIRECTION s
int w
config & add_child(config_key_type key)
Definition: config.cpp:514
std::vector< std::pair< const std::string *, const configr_of * > > subtags_
void read_compressed(config &cfg, std::istream &file, abstract_validator *validator)
Definition: parser.cpp:639
void write_open_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:703
Standard logging facilities (interface).
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
void write_bz2(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:788
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
mock_char c
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410