34 #include <boost/algorithm/string/replace.hpp>
35 #include <boost/iostreams/filter/bzip2.hpp>
36 #include <boost/iostreams/filtering_stream.hpp>
40 #pragma warning(disable : 4456)
41 #pragma warning(disable : 4458)
44 #include <boost/iostreams/filter/gzip.hpp>
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)
69 parser(
const parser&) =
delete;
70 parser& operator=(
const parser&) =
delete;
76 , validator_(validator)
89 void parse_variable();
92 const std::string& lineno,
93 const std::string& error_string,
94 const std::string& hint_string =
"",
95 const std::string& debug_string =
"");
97 void error(
const std::string& message,
const std::string& pos_format =
"");
101 element(
config* cfg,
const std::string& name,
int start_line = 0,
const std::string& file =
"")
104 , start_line(start_line)
119 std::stack<element> elements;
122 void parser::operator()()
125 elements.emplace(&cfg_,
"");
128 validator_->
open_tag(
"", cfg_, tok_.get_start_line(), tok_.get_file());
134 switch(tok_.current_token().type) {
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
152 std::stringstream ss;
153 ss << tok_.get_start_line() <<
" " << tok_.get_file();
156 error(
_(
"Unexpected characters at line start"));
164 }
while(tok_.current_token().type !=
token::END);
167 assert(!elements.empty());
170 element& el = elements.top();
171 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
172 validator_->close_tag();
175 if(elements.size() != 1) {
177 i18n_symbols[
"tag"] = elements.top().name;
179 std::stringstream ss;
180 ss << elements.top().start_line <<
" " << elements.top().file;
183 _(
"Missing closing tag for tag [$tag]"),
184 _(
"expected at $pos")),
190 void parser::parse_element()
195 config* current_element =
nullptr;
198 switch(tok_.current_token().type) {
200 elname = tok_.current_token().value;
202 if(tok_.next_token().type !=
']') {
203 error(
_(
"Unterminated [element] tag"));
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());
212 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
219 error(
_(
"Invalid tag name"));
222 elname = tok_.current_token().value;
224 if(tok_.next_token().type !=
']') {
225 error(
_(
"Unterminated [+element] tag"));
229 parent = elements.top().cfg;
231 current_element =
c.ptr();
234 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file(),
true);
237 current_element = &parent->
add_child(elname);
240 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
244 elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
249 error(
_(
"Invalid closing tag name"));
252 elname = tok_.current_token().value;
254 if(tok_.next_token().type !=
']') {
255 error(
_(
"Unterminated closing tag"));
258 if(elements.size() <= 1) {
259 error(
_(
"Unexpected closing tag"));
262 if(elname != elements.top().name) {
264 i18n_symbols[
"tag1"] = elements.top().name;
265 i18n_symbols[
"tag2"] = elname;
267 std::stringstream ss;
268 ss << elements.top().start_line <<
" " << elements.top().file;
271 _(
"Found invalid closing tag [/$tag2] for tag [$tag1]"),
272 _(
"opened at $pos")),
278 element& el = elements.top();
279 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
280 validator_->close_tag();
287 error(
_(
"Invalid tag name"));
291 void parser::parse_variable()
293 config& cfg = *elements.top().cfg;
294 std::vector<std::string> variables;
295 variables.emplace_back();
297 while(tok_.current_token().type !=
'=') {
298 switch(tok_.current_token().type) {
300 if(!variables.back().empty()) {
301 variables.back() +=
' ';
304 variables.back() += tok_.current_token().value;
308 if(variables.back().empty()) {
309 error(
_(
"Empty variable name"));
311 variables.emplace_back();
317 error(
_(
"Unexpected characters after variable name (expected , or =)"));
324 if(variables.back().empty()) {
325 error(
_(
"Empty variable name"));
330 std::vector<std::string>::const_iterator curvar = variables.begin();
332 bool ignore_next_newlines =
false, previous_string =
false;
336 assert(curvar != variables.end());
338 switch(tok_.current_token().type) {
340 if((curvar + 1) != variables.end()) {
344 cfg[*curvar] = buffer.
value();
348 validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
362 switch(tok_.current_token().type) {
364 error(
_(
"Unterminated quoted string"));
368 buffer +=
t_string_base(tok_.current_token().value, tok_.textdomain());
373 buffer += tok_.current_token().
value;
385 ignore_next_newlines =
true;
389 if(previous_string) {
396 buffer += tok_.current_token().
value;
400 buffer += tok_.current_token().
value;
404 error(
_(
"Unterminated quoted string"));
408 if(ignore_next_newlines) {
418 previous_string = tok_.current_token().type ==
token::STRING;
419 ignore_next_newlines =
false;
427 cfg[*curvar] = buffer.
value();
431 validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
434 while(++curvar != variables.end()) {
443 const std::string& lineno,
444 const std::string& error_string,
445 const std::string& hint_string,
446 const std::string& debug_string)
449 std::string result = error_string;
451 if(!hint_string.empty()) {
452 result +=
'\n' + hint_string;
455 if(!debug_string.empty()) {
456 result +=
'\n' + debug_string;
459 for(utils::string_map::value_type& var : i18n_symbols) {
460 boost::algorithm::replace_all(result, std::string(
"$") + var.first, std::string(var.second));
466 void parser::error(
const std::string& error_type,
const std::string& pos_format)
468 std::string hint_string = pos_format;
470 if(hint_string.empty()) {
471 hint_string =
_(
"at $pos");
475 i18n_symbols[
"error"] = error_type;
477 std::stringstream ss;
478 ss << tok_.get_start_line() <<
" " << tok_.get_file();
480 #ifdef DEBUG_TOKENIZER
481 i18n_symbols[
"value"] = tok_.current_token().value;
482 i18n_symbols[
"previous_value"] = tok_.previous_token().value;
484 const std::string& tok_state =
_(
"Value: '$value' Previous: '$previous_value'");
486 const std::string& tok_state =
"";
489 const std::string& message =
lineno_string(i18n_symbols, ss.str(),
"$error", hint_string, tok_state);
503 std::string escaped_string(
const std::string::const_iterator& begin,
const std::string::const_iterator& end)
506 std::string::const_iterator iter = begin;
509 const char c = *iter;
510 res.append(
c ==
'"' ? 2 : 1,
c);
521 inline std::string escaped_string(
const std::string& value)
523 return escaped_string(value.begin(), value.end());
526 class write_key_val_visitor
527 #ifdef USING_BOOST_VARIANT
528 :
public boost::static_visitor<void>
532 write_key_val_visitor(std::ostream& out,
unsigned level, std::string& textdomain,
const std::string& key)
535 , textdomain_(textdomain)
542 void operator()(
const T& v)
const
545 out_ << key_ <<
'=' << v <<
'\n';
552 void operator()(
const utils::monostate&)
const
557 void operator()(
const std::string&
s)
const
560 out_ << key_ <<
'=' <<
'"' << escaped_string(
s) <<
'"' <<
'\n';
563 void operator()(
const t_string&
s)
const;
568 for(
unsigned i = 0;
i < level_; ++
i) {
574 const unsigned level_;
575 std::string& textdomain_;
576 const std::string& key_;
587 void write_key_val_visitor::operator()(
const t_string& value)
const
596 if(
w.translatable() &&
w.textdomain() != textdomain_) {
597 textdomain_ =
w.textdomain();
598 out_ <<
"#textdomain " << textdomain_ <<
'\n';
609 if(
w.translatable()) {
613 out_ <<
'"' << escaped_string(
w.begin(),
w.end()) <<
'"';
629 parser(cfg,
in, validator)();
634 std::istringstream ss(
in);
635 parser(cfg, ss, validator)();
638 template<
typename decompressor>
642 if(file.peek() == EOF) {
646 boost::iostreams::filtering_stream<boost::iostreams::input> filter;
647 filter.push(decompressor());
659 filter.exceptions(filter.exceptions() | std::ios_base::badbit);
669 if(filter.peek() == EOF) {
670 LOG_CF <<
"Empty compressed file or error at reading a compressed file.";
675 LOG_CF <<
" filter.peek() != EOF but !filter.good()."
676 <<
"This indicates a malformed gz stream and can make Wesnoth crash.";
679 parser(cfg, filter, validator)();
685 read_compressed<boost::iostreams::gzip_decompressor>(cfg, file, validator);
691 read_compressed<boost::iostreams::bzip2_decompressor>(cfg, file, validator);
695 const std::string& key,
698 std::string& textdomain)
705 out << std::string(
level,
'\t') <<
'[' << child <<
"]\n";
710 out << std::string(
level,
'\t') <<
"[/" << child <<
"]\n";
716 throw config::error(
"Too many recursion levels in config write");
721 ERR_CF <<
"Config contains invalid attribute name '" <<
i.first <<
"', skipping...";
730 ERR_CF <<
"Config contains invalid tag name '" <<
item.key <<
"', skipping...";
743 throw config::error(
"Too many recursion levels in config write");
750 for(
const auto& pair : cfg.
subtags_) {
751 assert(pair.first && pair.second);
754 ERR_CF <<
"Config contains invalid tag name '" << *pair.first <<
"', skipping...";
766 std::string textdomain =
PACKAGE;
770 template<
typename compressor>
773 boost::iostreams::filtering_stream<boost::iostreams::output> filter;
774 filter.push(compressor());
785 write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
790 write_compressed<boost::iostreams::bzip2_compressor>(out, cfg);
Used in parsing config file.
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.
const_attr_itors attribute_range() const
static bool valid_tag(config_key_type name)
const_all_children_itors all_children_range() const
In-order iteration over all children.
static bool valid_attribute(config_key_type name)
attribute_map::value_type attribute
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
config & add_child(config_key_type key)
bool translatable() const
const std::string & value() const
Abstract baseclass for the tokenizer.
unsigned in
If equal to search_counter, the node is off the list.
static std::string _(const char *str)
Standard logging facilities (interface).
std::pair< std::string, unsigned > item
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)
void read_compressed(config &cfg, std::istream &file, abstract_validator *validator)
void write_compressed(std::ostream &out, const configr_of &cfg)
void read(config &cfg, std::istream &in, abstract_validator *validator)
static const std::size_t max_recursion_levels
void read_bz2(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
void write_close_child(std::ostream &out, const std::string &child, unsigned int level)
static void write_internal(const config &cfg, std::ostream &out, std::string &textdomain, std::size_t tab=0)
void write_key_val(std::ostream &out, const std::string &key, const config::attribute_value &value, unsigned level, std::string &textdomain)
void write_bz2(std::ostream &out, const configr_of &cfg)
void write_open_child(std::ostream &out, const std::string &child, unsigned int level)
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
void read_gz(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
static lg::log_domain log_config("config")
std::vector< std::pair< const std::string *, const configr_of * > > subtags_
static map_location::DIRECTION s
This file contains information about validation abstract level interface.
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.