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)
81 void parse_variable();
84 const std::string& lineno,
85 const std::string& error_string,
86 const std::string& hint_string =
"",
87 const std::string& debug_string =
"");
90 [[noreturn]]
void error(
const std::string& message,
const std::string& pos_format =
"");
94 #ifndef __cpp_aggregate_paren_init
95 element(
config*
cfg,
const std::string& name,
int start_line = 0,
const std::string& file =
"")
98 , start_line(start_line)
113 std::stack<element> elements;
116 config parser::operator()()
119 elements.emplace(&res,
"");
122 validator_->open_tag(
"", res, tok_.get_start_line(), tok_.get_file());
128 switch(tok_.current_token().type) {
141 if(
static_cast<unsigned char>(tok_.current_token().value[0]) == 0xEF &&
142 static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBB &&
143 static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBF
146 std::stringstream ss;
147 ss << tok_.get_start_line() <<
" " << tok_.get_file();
150 error(
_(
"Unexpected characters at line start"));
158 }
while(tok_.current_token().type !=
token::END);
161 assert(!elements.empty());
164 element& el = elements.top();
165 validator_->validate(*el.cfg, el.name, el.start_line, el.file);
166 validator_->close_tag();
169 if(elements.size() != 1) {
171 i18n_symbols[
"tag"] = elements.top().name;
173 std::stringstream ss;
174 ss << elements.top().start_line <<
" " << elements.top().file;
177 _(
"Missing closing tag for tag [$tag]"),
178 _(
"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()
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()) {
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;
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 =
"";
493 class write_key_val_visitor
494 #ifdef USING_BOOST_VARIANT
495 :
public boost::static_visitor<void>
499 write_key_val_visitor(std::ostream& out,
unsigned level, std::string& textdomain,
const std::string& key)
502 , textdomain_(textdomain)
509 void operator()(
const T& v)
const
512 if constexpr(std::is_arithmetic_v<T>) {
515 out_ << key_ <<
'=' << buf.get_view() <<
'\n';
517 out_ << key_ <<
'=' << v <<
'\n';
525 void operator()(
const utils::monostate&)
const
530 void operator()(
const std::string&
s)
const
536 void operator()(
const t_string&
s)
const;
541 for(
unsigned i = 0;
i < level_; ++
i) {
547 const unsigned level_;
548 std::string& textdomain_;
549 const std::string& key_;
560 void write_key_val_visitor::operator()(
const t_string& value)
const
569 if(
w.translatable() &&
w.textdomain() != textdomain_) {
570 textdomain_ =
w.textdomain();
571 out_ <<
"#textdomain " << textdomain_ <<
'\n';
582 if(
w.translatable()) {
607 std::istringstream ss(
in);
611 template<
typename Decompressor>
615 if(file.peek() == EOF) {
619 boost::iostreams::filtering_stream<boost::iostreams::input>
filter;
620 filter.push(Decompressor());
632 filter.exceptions(
filter.exceptions() | std::ios_base::badbit);
642 if(
filter.peek() == EOF) {
643 LOG_CF <<
"Empty compressed file or error at reading a compressed file.";
648 LOG_CF <<
" filter.peek() != EOF but !filter.good()."
649 <<
"This indicates a malformed gz stream and can make Wesnoth crash.";
658 return read_compressed<boost::iostreams::gzip_decompressor>(file,
validator);
664 return read_compressed<boost::iostreams::bzip2_decompressor>(file,
validator);
668 const std::string& key,
671 std::string& textdomain)
678 out << std::string(
level,
'\t') <<
'[' << child <<
"]\n";
683 out << std::string(
level,
'\t') <<
"[/" << child <<
"]\n";
689 throw config::error(
"Too many recursion levels in config write");
694 ERR_CF <<
"Config contains invalid attribute name '" << key <<
"', skipping...";
703 ERR_CF <<
"Config contains invalid tag name '" << key <<
"', skipping...";
716 throw config::error(
"Too many recursion levels in config write");
723 for(
const auto& pair :
cfg.subtags_) {
724 assert(pair.first && pair.second);
727 ERR_CF <<
"Config contains invalid tag name '" << *pair.first <<
"', skipping...";
739 std::string textdomain =
PACKAGE;
743 template<
typename Compressor>
746 boost::iostreams::filtering_stream<boost::iostreams::output>
filter;
747 filter.push(Compressor());
758 write_compressed<boost::iostreams::gzip_compressor>(out,
cfg);
763 write_compressed<boost::iostreams::bzip2_compressor>(out,
cfg);
unsigned in
If equal to search_counter, the node is off the list.
Used in parsing config file.
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).
static std::string _(const char *str)
Standard logging facilities (interface).
config read(std::istream &in, abstract_validator *validator)
void write_open_child(std::ostream &out, const std::string &child, unsigned int level)
config read_compressed(std::istream &file, abstract_validator *validator)
void write_close_child(std::ostream &out, const std::string &child, unsigned int level)
config read_bz2(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
void write_bz2(std::ostream &out, const configr_of &cfg)
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
config read_gz(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
static void write_internal(const config &cfg, std::ostream &out, std::string &textdomain, std::size_t tab=0)
void write_compressed(std::ostream &out, const configr_of &cfg)
void write_gz(std::ostream &out, const configr_of &cfg)
void write_key_val(std::ostream &out, const std::string &key, const config::attribute_value &value, unsigned level, std::string &textdomain)
std::map< std::string, t_string > string_map
std::string wml_escape_string(std::string_view str)
Format str as a WML value
std::string lineno_string(const std::string &lineno)
constexpr std::size_t max_recursion_levels
static lg::log_domain log_config("config")
@ 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.