33 #include <boost/algorithm/string/replace.hpp>
34 #include <boost/iostreams/filter/bzip2.hpp>
35 #include <boost/iostreams/filtering_stream.hpp>
39 #pragma warning(disable : 4456)
40 #pragma warning(disable : 4458)
43 #include <boost/iostreams/filter/gzip.hpp>
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)
68 parser(
const parser&) =
delete;
69 parser& operator=(
const parser&) =
delete;
84 void parse_variable();
87 const std::string& lineno,
88 const std::string& error_string,
89 const std::string& hint_string =
"",
90 const std::string& debug_string =
"");
92 void error(
const std::string& message,
const std::string& pos_format =
"");
96 element(
config* cfg,
const std::string& name,
int start_line = 0,
const std::string& file =
"")
99 , start_line(start_line)
114 std::stack<element> elements;
117 void parser::operator()()
120 elements.emplace(&cfg_,
"");
123 validator_->
open_tag(
"", cfg_, tok_.get_start_line(), tok_.get_file());
129 switch(tok_.current_token().type) {
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
147 std::stringstream ss;
148 ss << tok_.get_start_line() <<
" " << tok_.get_file();
151 error(
_(
"Unexpected characters at line start"));
159 }
while(tok_.current_token().type !=
token::END);
162 assert(!elements.empty());
165 element& el = elements.top();
166 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
167 validator_->close_tag();
170 if(elements.size() != 1) {
172 i18n_symbols[
"tag"] = elements.top().name;
174 std::stringstream ss;
175 ss << elements.top().start_line <<
" " << elements.top().file;
178 _(
"Missing closing tag for tag [$tag]"),
179 _(
"expected at $pos")),
185 void parser::parse_element()
190 config* current_element =
nullptr;
193 switch(tok_.current_token().type) {
195 elname = tok_.current_token().value;
198 error(
_(
"Unterminated [element] tag"));
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());
207 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
214 error(
_(
"Invalid tag name"));
217 elname = tok_.current_token().value;
220 error(
_(
"Unterminated [+element] tag"));
224 parent = elements.top().cfg;
226 current_element =
c.ptr();
229 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file(),
true);
232 current_element = &parent->
add_child(elname);
235 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
239 elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
244 error(
_(
"Invalid closing tag name"));
247 elname = tok_.current_token().value;
250 error(
_(
"Unterminated closing tag"));
253 if(elements.size() <= 1) {
254 error(
_(
"Unexpected closing tag"));
257 if(elname != elements.top().name) {
259 i18n_symbols[
"tag1"] = elements.top().name;
260 i18n_symbols[
"tag2"] = elname;
262 std::stringstream ss;
263 ss << elements.top().start_line <<
" " << elements.top().file;
266 _(
"Found invalid closing tag [/$tag2] for tag [$tag1]"),
267 _(
"opened at $pos")),
273 element& el = elements.top();
274 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
275 validator_->close_tag();
282 error(
_(
"Invalid tag name"));
286 void parser::parse_variable()
288 config& cfg = *elements.top().cfg;
289 std::vector<std::string> variables;
290 variables.emplace_back();
293 switch(tok_.current_token().type) {
295 if(!variables.back().empty()) {
296 variables.back() +=
' ';
299 variables.back() += tok_.current_token().value;
303 if(variables.back().empty()) {
304 error(
_(
"Empty variable name"));
306 variables.emplace_back();
312 error(
_(
"Unexpected characters after variable name (expected , or =)"));
319 if(variables.back().empty()) {
320 error(
_(
"Empty variable name"));
325 std::vector<std::string>::const_iterator curvar = variables.begin();
327 bool ignore_next_newlines =
false, previous_string =
false;
331 assert(curvar != variables.end());
333 switch(tok_.current_token().type) {
335 if((curvar + 1) != variables.end()) {
339 cfg[*curvar] = buffer.
value();
343 validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
357 switch(tok_.current_token().type) {
359 error(
_(
"Unterminated quoted string"));
363 buffer +=
t_string_base(tok_.current_token().value, tok_.textdomain());
368 buffer += tok_.current_token().
value;
380 ignore_next_newlines =
true;
384 if(previous_string) {
391 buffer += tok_.current_token().
value;
395 buffer += tok_.current_token().
value;
399 error(
_(
"Unterminated quoted string"));
403 if(ignore_next_newlines) {
413 previous_string = tok_.current_token().type ==
token::STRING;
414 ignore_next_newlines =
false;
422 cfg[*curvar] = buffer.
value();
426 validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
429 while(++curvar != variables.end()) {
438 const std::string& lineno,
439 const std::string& error_string,
440 const std::string& hint_string,
441 const std::string& debug_string)
444 std::string result = error_string;
446 if(!hint_string.empty()) {
447 result +=
'\n' + hint_string;
450 if(!debug_string.empty()) {
451 result +=
'\n' + debug_string;
454 for(utils::string_map::value_type& var : i18n_symbols) {
455 boost::algorithm::replace_all(result, std::string(
"$") + var.first, std::string(var.second));
461 void parser::error(
const std::string& error_type,
const std::string& pos_format)
463 std::string hint_string = pos_format;
465 if(hint_string.empty()) {
466 hint_string =
_(
"at $pos");
470 i18n_symbols[
"error"] = error_type;
472 std::stringstream ss;
473 ss << tok_.get_start_line() <<
" " << tok_.get_file();
475 #ifdef DEBUG_TOKENIZER
476 i18n_symbols[
"value"] = tok_.current_token().value;
477 i18n_symbols[
"previous_value"] = tok_.previous_token().value;
479 const std::string& tok_state =
_(
"Value: '$value' Previous: '$previous_value'");
481 const std::string& tok_state =
"";
484 const std::string& message =
lineno_string(i18n_symbols, ss.str(),
"$error", hint_string, tok_state);
498 std::string escaped_string(
const std::string::const_iterator& begin,
const std::string::const_iterator& end)
501 std::string::const_iterator iter = begin;
504 const char c = *iter;
505 res.append(
c ==
'"' ? 2 : 1,
c);
516 inline std::string escaped_string(
const std::string& value)
518 return escaped_string(value.begin(), value.end());
521 class write_key_val_visitor
522 #ifdef USING_BOOST_VARIANT
523 :
public boost::static_visitor<void>
527 write_key_val_visitor(std::ostream& out,
unsigned level, std::string& textdomain,
const std::string& key)
530 , textdomain_(textdomain)
537 void operator()(
const T& v)
const
540 out_ << key_ <<
'=' << v <<
'\n';
547 void operator()(
const utils::monostate&)
const
552 void operator()(
const std::string&
s)
const
555 out_ << key_ <<
'=' <<
'"' << escaped_string(
s) <<
'"' <<
'\n';
558 void operator()(
const t_string&
s)
const;
563 for(
unsigned i = 0;
i < level_; ++
i) {
569 const unsigned level_;
570 std::string& textdomain_;
571 const std::string& key_;
582 void write_key_val_visitor::operator()(
const t_string& value)
const
591 if(
w.translatable() &&
w.textdomain() != textdomain_) {
592 textdomain_ =
w.textdomain();
593 out_ <<
"#textdomain " << textdomain_ <<
'\n';
604 if(
w.translatable()) {
608 out_ <<
'"' << escaped_string(
w.begin(),
w.end()) <<
'"';
629 std::istringstream ss(
in);
633 template<
typename decompressor>
637 if(file.peek() == EOF) {
641 boost::iostreams::filtering_stream<boost::iostreams::input> filter;
642 filter.push(decompressor());
654 filter.exceptions(filter.exceptions() | std::ios_base::badbit);
664 if(filter.peek() == EOF) {
665 LOG_CF <<
"Empty compressed file or error at reading a compressed file.";
670 LOG_CF <<
" filter.peek() != EOF but !filter.good()."
671 <<
"This indicates a malformed gz stream and can make Wesnoth crash.";
680 read_compressed<boost::iostreams::gzip_decompressor>(cfg, file,
validator);
686 read_compressed<boost::iostreams::bzip2_decompressor>(cfg, file,
validator);
690 const std::string& key,
693 std::string& textdomain)
700 out << std::string(
level,
'\t') <<
'[' << child <<
"]\n";
705 out << std::string(
level,
'\t') <<
"[/" << child <<
"]\n";
711 throw config::error(
"Too many recursion levels in config write");
716 ERR_CF <<
"Config contains invalid attribute name '" << key <<
"', skipping...";
725 ERR_CF <<
"Config contains invalid tag name '" << key <<
"', skipping...";
738 throw config::error(
"Too many recursion levels in config write");
745 for(
const auto& pair : cfg.
subtags_) {
746 assert(pair.first && pair.second);
749 ERR_CF <<
"Config contains invalid tag name '" << *pair.first <<
"', skipping...";
761 std::string textdomain =
PACKAGE;
765 template<
typename compressor>
768 boost::iostreams::filtering_stream<boost::iostreams::output> filter;
769 filter.push(compressor());
780 write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
785 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)
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.
config & add_child(config_key_type key)
Helper class for translatable strings.
bool translatable() const
const std::string & value() const
class responsible for parsing the provided text into tokens and tracking information about the curren...
Definitions for the interface to Wesnoth Markup Language (WML).
unsigned in
If equal to search_counter, the node is off the list.
static std::string _(const char *str)
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)
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_
@ QSTRING
quoted string, contained within double quotes or by less than/greater than symbols
@ UNTERMINATED_QSTRING
reached end of file without finding the closing character for a QSTRING
@ END
set when EOF is returned by the input stream
static map_location::DIRECTION s
This file contains information about validation abstract level interface.
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.