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