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;
85 void parse_variable();
88 const std::string& lineno,
89 const std::string& error_string,
90 const std::string& hint_string =
"",
91 const std::string& debug_string =
"");
93 void error(
const std::string& message,
const std::string& pos_format =
"");
97 element(
config* cfg,
const std::string& name,
int start_line = 0,
const std::string& file =
"")
100 , start_line(start_line)
115 std::stack<element> elements;
118 void parser::operator()()
121 elements.emplace(&cfg_,
"");
124 validator_->
open_tag(
"", cfg_, tok_.get_start_line(), tok_.get_file());
130 switch(tok_.current_token().type) {
143 if(
static_cast<unsigned char>(tok_.current_token().value[0]) == 0xEF &&
144 static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBB &&
145 static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBF
148 std::stringstream ss;
149 ss << tok_.get_start_line() <<
" " << tok_.get_file();
152 error(
_(
"Unexpected characters at line start"));
160 }
while(tok_.current_token().type !=
token::END);
163 assert(!elements.empty());
166 element& el = elements.top();
167 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
168 validator_->close_tag();
171 if(elements.size() != 1) {
173 i18n_symbols[
"tag"] = elements.top().name;
175 std::stringstream ss;
176 ss << elements.top().start_line <<
" " << elements.top().file;
179 _(
"Missing closing tag for tag [$tag]"),
180 _(
"expected at $pos")),
186 void parser::parse_element()
191 config* current_element =
nullptr;
194 switch(tok_.current_token().type) {
196 elname = tok_.current_token().value;
199 error(
_(
"Unterminated [element] tag"));
203 parent = elements.top().cfg;
204 current_element = &(parent->
add_child(elname));
205 elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
208 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
215 error(
_(
"Invalid tag name"));
218 elname = tok_.current_token().value;
221 error(
_(
"Unterminated [+element] tag"));
225 parent = elements.top().cfg;
227 current_element =
c.ptr();
230 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file(),
true);
233 current_element = &parent->
add_child(elname);
236 validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
240 elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
245 error(
_(
"Invalid closing tag name"));
248 elname = tok_.current_token().value;
251 error(
_(
"Unterminated closing tag"));
254 if(elements.size() <= 1) {
255 error(
_(
"Unexpected closing tag"));
258 if(elname != elements.top().name) {
260 i18n_symbols[
"tag1"] = elements.top().name;
261 i18n_symbols[
"tag2"] = elname;
263 std::stringstream ss;
264 ss << elements.top().start_line <<
" " << elements.top().file;
267 _(
"Found invalid closing tag [/$tag2] for tag [$tag1]"),
268 _(
"opened at $pos")),
274 element& el = elements.top();
275 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
276 validator_->close_tag();
283 error(
_(
"Invalid tag name"));
287 void parser::parse_variable()
289 config& cfg = *elements.top().cfg;
290 std::vector<std::string> variables;
291 variables.emplace_back();
294 switch(tok_.current_token().type) {
296 if(!variables.back().empty()) {
297 variables.back() +=
' ';
300 variables.back() += tok_.current_token().value;
304 if(variables.back().empty()) {
305 error(
_(
"Empty variable name"));
307 variables.emplace_back();
313 error(
_(
"Unexpected characters after variable name (expected , or =)"));
320 if(variables.back().empty()) {
321 error(
_(
"Empty variable name"));
326 std::vector<std::string>::const_iterator curvar = variables.begin();
328 bool ignore_next_newlines =
false, previous_string =
false;
332 assert(curvar != variables.end());
334 switch(tok_.current_token().type) {
336 if((curvar + 1) != variables.end()) {
340 cfg[*curvar] = buffer.
value();
344 validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
358 switch(tok_.current_token().type) {
360 error(
_(
"Unterminated quoted string"));
364 buffer +=
t_string_base(tok_.current_token().value, tok_.textdomain());
369 buffer += tok_.current_token().
value;
381 ignore_next_newlines =
true;
385 if(previous_string) {
392 buffer += tok_.current_token().
value;
396 buffer += tok_.current_token().
value;
400 error(
_(
"Unterminated quoted string"));
404 if(ignore_next_newlines) {
414 previous_string = tok_.current_token().type ==
token::STRING;
415 ignore_next_newlines =
false;
423 cfg[*curvar] = buffer.
value();
427 validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
430 while(++curvar != variables.end()) {
439 const std::string& lineno,
440 const std::string& error_string,
441 const std::string& hint_string,
442 const std::string& debug_string)
445 std::string result = error_string;
447 if(!hint_string.empty()) {
448 result +=
'\n' + hint_string;
451 if(!debug_string.empty()) {
452 result +=
'\n' + debug_string;
455 for(utils::string_map::value_type& var : i18n_symbols) {
456 boost::algorithm::replace_all(result, std::string(
"$") + var.first, std::string(var.second));
462 void parser::error(
const std::string& error_type,
const std::string& pos_format)
464 std::string hint_string = pos_format;
466 if(hint_string.empty()) {
467 hint_string =
_(
"at $pos");
471 i18n_symbols[
"error"] = error_type;
473 std::stringstream ss;
474 ss << tok_.get_start_line() <<
" " << tok_.get_file();
476 #ifdef DEBUG_TOKENIZER
477 i18n_symbols[
"value"] = tok_.current_token().value;
478 i18n_symbols[
"previous_value"] = tok_.previous_token().value;
480 const std::string& tok_state =
_(
"Value: ‘$value’ Previous: ‘$previous_value’");
482 const std::string& tok_state =
"";
485 const std::string& message =
lineno_string(i18n_symbols, ss.str(),
"$error", hint_string, tok_state);
499 std::string escaped_string(
const std::string::const_iterator& begin,
const std::string::const_iterator& end)
502 std::string::const_iterator iter = begin;
505 const char c = *iter;
506 res.append(
c ==
'"' ? 2 : 1,
c);
517 inline std::string escaped_string(
const std::string& value)
519 return escaped_string(value.begin(), value.end());
522 class write_key_val_visitor
523 #ifdef USING_BOOST_VARIANT
524 :
public boost::static_visitor<void>
528 write_key_val_visitor(std::ostream& out,
unsigned level, std::string& textdomain,
const std::string& key)
531 , textdomain_(textdomain)
538 void operator()(
const T& v)
const
541 if constexpr(std::is_arithmetic_v<T>) {
544 out_ << key_ <<
'=' << buf.get_view() <<
'\n';
546 out_ << key_ <<
'=' << v <<
'\n';
554 void operator()(
const utils::monostate&)
const
559 void operator()(
const std::string&
s)
const
562 out_ << key_ <<
'=' <<
'"' << escaped_string(
s) <<
'"' <<
'\n';
565 void operator()(
const t_string&
s)
const;
570 for(
unsigned i = 0;
i < level_; ++
i) {
576 const unsigned level_;
577 std::string& textdomain_;
578 const std::string& key_;
589 void write_key_val_visitor::operator()(
const t_string& value)
const
598 if(
w.translatable() &&
w.textdomain() != textdomain_) {
599 textdomain_ =
w.textdomain();
600 out_ <<
"#textdomain " << textdomain_ <<
'\n';
611 if(
w.translatable()) {
615 out_ <<
'"' << escaped_string(
w.begin(),
w.end()) <<
'"';
636 std::istringstream ss(
in);
640 template<
typename decompressor>
644 if(file.peek() == EOF) {
648 boost::iostreams::filtering_stream<boost::iostreams::input> filter;
649 filter.push(decompressor());
661 filter.exceptions(filter.exceptions() | std::ios_base::badbit);
671 if(filter.peek() == EOF) {
672 LOG_CF <<
"Empty compressed file or error at reading a compressed file.";
677 LOG_CF <<
" filter.peek() != EOF but !filter.good()."
678 <<
"This indicates a malformed gz stream and can make Wesnoth crash.";
687 read_compressed<boost::iostreams::gzip_decompressor>(cfg, file,
validator);
693 read_compressed<boost::iostreams::bzip2_decompressor>(cfg, file,
validator);
697 const std::string& key,
700 std::string& textdomain)
707 out << std::string(
level,
'\t') <<
'[' << child <<
"]\n";
712 out << std::string(
level,
'\t') <<
"[/" << child <<
"]\n";
718 throw config::error(
"Too many recursion levels in config write");
723 ERR_CF <<
"Config contains invalid attribute name '" << key <<
"', skipping...";
732 ERR_CF <<
"Config contains invalid tag name '" << key <<
"', skipping...";
745 throw config::error(
"Too many recursion levels in config write");
752 for(
const auto& pair : cfg.
subtags_) {
753 assert(pair.first && pair.second);
756 ERR_CF <<
"Config contains invalid tag name '" << *pair.first <<
"', skipping...";
768 std::string textdomain =
PACKAGE;
772 template<
typename compressor>
775 boost::iostreams::filtering_stream<boost::iostreams::output> filter;
776 filter.push(compressor());
787 write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
792 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
auto all_children_view() const
In-order iteration over all children.
static bool valid_tag(config_key_type name)
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.