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