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