25 #include <boost/graph/adjacency_list.hpp>
32 #define ERR_VL LOG_STREAM(err, log_validation)
33 #define WRN_VL LOG_STREAM(warn, log_validation)
34 #define LOG_VL LOG_STREAM(info, log_validation)
36 static std::string
at(
const std::string& file,
int line)
38 std::ostringstream ss;
39 ss <<
line <<
" " << file;
43 static void print_output(
const std::string& message,
bool flag_exception =
false)
45 #ifndef VALIDATION_ERRORS_LOG
53 flag_exception =
true;
62 const std::string& name,
64 const std::string& parent,
67 std::ostringstream ss;
68 ss <<
"Extra tag [" << name <<
"]; there may only be " <<
n <<
" [" << name <<
"] in [" << parent <<
"]\n"
75 const std::string& file,
int line,
const std::string& name,
const std::string& parent,
bool flag_exception)
77 std::ostringstream ss;
78 ss <<
"Tag [" << name <<
"] may not be used in [" << parent <<
"]\n" <<
at(file,
line) <<
"\n";
85 const std::string& name,
87 const std::string& parent,
90 std::ostringstream ss;
91 ss <<
"Missing tag [" << name <<
"]; there must be " <<
n <<
" [" << name <<
"]s in [" << parent <<
"]\n"
98 const std::string& file,
int line,
const std::string& tag,
const std::string& key,
bool flag_exception)
100 std::ostringstream ss;
101 ss <<
"Invalid key '" << key <<
"='";
103 ss <<
" in tag [" << tag <<
"]\n";
106 ss <<
at(file,
line) <<
"\n";
113 const std::string& file,
int line,
const std::string& tag,
const std::string& key,
bool flag_exception)
115 std::ostringstream ss;
116 ss <<
"Missing key '" << key <<
"='";
118 ss <<
" in tag [" << tag <<
"]\n";
121 ss <<
at(file,
line) <<
"\n";
129 const std::string& tag,
130 const std::string& key,
131 const std::string& value,
132 const std::string& expected,
135 std::ostringstream ss;
136 ss <<
"Invalid value '";
137 if(value.length() > 128)
138 ss << value.substr(0, 128) <<
"...";
140 ss <<
"' in key '" << key <<
"=' in tag [" << tag <<
"]\n" <<
" (expected value of type " << expected <<
") " <<
at(file,
line) <<
"\n";
147 const std::string& tag,
148 const std::string& schema_name,
149 const std::string& value,
152 std::ostringstream ss;
153 ss <<
"Inheritance cycle from " << tag <<
" to " << value <<
" found\n"
154 <<
at(file,
line) <<
"\nwith schema " << schema_name <<
"\n";
161 const std::string& tag,
162 const std::string& value,
165 std::ostringstream ss;
166 ss <<
"Link cycle from " << tag <<
" to " << value <<
" found\n"
174 const std::string& tag,
175 const std::string& schema_name,
176 const std::string& super,
179 std::ostringstream ss;
180 ss <<
"Super " << super <<
" not found. Needed by " << tag <<
"\n"
181 <<
at(file,
line) <<
"\nwith schema " << schema_name <<
"\n";
188 const std::string& tag,
189 const std::string& key,
190 const std::string& value,
193 std::ostringstream ss;
194 ss <<
"Unknown path reference '" << value <<
"' in key '" << key <<
"=' in tag [" << tag <<
"]\n" <<
at(file,
line) <<
"\n";
200 const std::string& tag,
201 const std::string& pat,
202 const std::string& value,
205 std::ostringstream ss;
206 ss <<
"Duplicate or fully-overlapping tag definition '" << value <<
"' (which is also matched by '" << pat <<
"') in tag [" << tag <<
"]\n" <<
at(file,
line) <<
"\n";
212 const std::string& tag,
213 const std::string& pat,
214 const std::string& value,
217 std::ostringstream ss;
218 ss <<
"Duplicate or fully-overlapping key definition '" << value <<
"' (which is also matched by '" << pat <<
"') in tag [" << tag <<
"]\n" <<
at(file,
line) <<
"\n";
224 const std::string& tag,
225 const std::string& key,
226 const std::string& value,
230 std::ostringstream ss;
231 ss <<
"Inheritance loop " << key <<
"=" << value <<
" found (at offset " <<
index <<
") in tag [" << tag <<
"]\n" <<
at(file,
line) <<
"\n";
236 const std::string & tag,
237 const std::string & key,
238 const std::string &
type,
241 std::ostringstream ss;
242 ss <<
"Invalid type '" <<
type <<
"' in key '" << key <<
"=' in tag [" << tag <<
"]\n" <<
at(file,
line) <<
"\n";
253 , config_read_(false)
254 , validate_schema_(validate_schema)
258 ERR_VL <<
"Schema file " << config_file_name <<
" was not read.";
265 LOG_VL <<
"Schema file " << config_file_name <<
" was read.";
266 LOG_VL <<
"Validator initialized";
274 std::unique_ptr<abstract_validator> validator;
280 read(cfg, *stream, validator.
get());
282 ERR_VL <<
"Failed to read file " << filename <<
":\n" <<
e.what();
287 for(
const config& schema :
g.child_range(
"tag")) {
288 if(schema[
"name"].str() ==
"root") {
293 types_[
"t_string"] = std::make_shared<wml_type_tstring>();
297 }
catch(
const std::exception&) {
317 boost::depth_first_search(link_graph,
319 const auto source = std::find_if(link_map.begin(), link_map.end(),
320 [&](
const auto& link) { return link.second == boost::source(edge, link_graph); });
322 assert(source != link_map.end());
324 const auto target = std::find_if(link_map.begin(), link_map.end(),
325 [&](
const auto& link) { return link.second == boost::target(edge, link_graph); });
327 assert(target != link_map.end());
329 const auto& alias_type = link_graph[source->second];
330 const auto& link_type = link_graph[target->second];
338 auto it =
types_.find(alias->link());
344 for(
auto elem : composite->subtypes()) {
352 if (link_map.find(alias) == link_map.end()) {
355 boost::add_vertex(type_name, link_graph));
358 if (link_map.find(link) == link_map.end()) {
361 boost::add_vertex(alias->
link(), link_graph));
364 boost::add_edge(link_map[alias], link_map[link], link_graph);
366 for(
auto elem : composite->subtypes()) {
381 }
else if(!
stack_.empty()) {
421 for(
auto& m :
cache_.top()) {
422 for(
auto& list : m.second) {
436 auto cache_it =
cache_.top().find(&cfg);
437 if(cache_it !=
cache_.top().end()) {
438 cache_it->second.clear();
446 if(&active == &
root_) {
450 const auto super_tags = active.
super(cfg);
452 for(
const auto& [super_path, super_tag] : super_tags) {
469 for(
const auto& expected : super_expected) {
470 const auto super_exists = std::any_of(super_tags.begin(), super_tags.end(),
471 [&](
const std::pair<std::string, const wml_tag*>& super) { return super.first == expected; });
479 for(
const auto& tag : active.
tags(cfg)) {
480 int cnt =
counter_.top()[tag.first].cnt;
482 if(tag.second.get_min() > cnt) {
487 if(tag.second.get_max() < cnt) {
492 int total_cnt =
counter_.top()[
""].cnt;
495 }
else if(
active_tag().get_max_children() < total_cnt) {
506 auto visited = std::vector<const wml_tag*>();
511 const wml_tag* tag,
const config& cfg, std::vector<const wml_tag*>& visited)
const
514 if(std::find(visited.begin(), visited.end(), tag) != visited.end()) {
518 visited.push_back(tag);
520 auto mandatory_keys = std::map<std::string, wml_key>();
523 for(
const auto& [
_, super_tag] : tag->
super(cfg)) {
527 if(!super_mandatory_keys) {
531 super_mandatory_keys->merge(mandatory_keys);
532 mandatory_keys.swap(*super_mandatory_keys);
536 for(
const auto& key : tag->
keys(cfg)) {
537 if(key.second.is_mandatory() || mandatory_keys.find(key.first) != mandatory_keys.end()) {
538 mandatory_keys[key.first] = key.second;
542 return mandatory_keys;
546 const wml_tag* tag,
const config& cfg,
const std::string& name,
int start_line,
const std::string& file)
551 if(!mandatory_keys) {
555 auto visited = std::vector<const wml_tag*>();
562 const std::string& name,
564 const std::string& file,
565 std::vector<const wml_tag*>& visited)
568 if(std::find(visited.begin(), visited.end(), tag) != visited.end()) {
572 visited.push_back(tag);
575 for(
const auto& key : mandatory_keys) {
576 if(key.second.is_mandatory()) {
577 if(cfg.
get(key.first) ==
nullptr) {
589 [&](
const auto& derivation) { return derivation.second == boost::source(edge, derivation_graph_); });
594 [&](
const auto& derivation) { return derivation.second == boost::target(edge, derivation_graph_); });
614 bool matched =
false;
634 assert(
have_active_tag() &&
"Tried to get active tag name when there was none");
653 std::stack<const wml_tag*> temp =
stack_;
654 std::deque<std::string>
path;
655 while(!temp.empty()) {
656 path.push_front(temp.top()->get_name());
659 if(
path.front() ==
"root") {
698 , condition_nesting_()
711 if(name ==
"if" || name ==
"switch") {
713 }
else if(name ==
"tag") {
725 if(tag_name ==
"type") {
739 std::string suffix =
path.back();
741 while(!
path.empty()) {
743 auto link =
links_.find(prefix);
744 if(link !=
links_.end()) {
745 std::string new_path = link->second +
"/" + suffix;
750 suffix =
path.back();
754 if(supers.first != supers.second) {
756 for(
auto cur = supers.first ; cur != supers.second; ++cur) {
757 super_ref.
value_ = cur->second +
"/" + suffix;
766 std::string new_path = prefix +
"/" + suffix;
770 suffix =
path.back() +
"/" + suffix;
787 for(
const std::string& pattern : seen) {
788 for(
const std::string& key :
split) {
795 seen.push_back(name);
802 }
else if(name ==
"tag") {
803 bool first_tag =
true, first_key =
true;
804 std::vector<std::string> tag_names, key_names;
806 if(current.key ==
"tag" || current.key ==
"link") {
807 std::string tag_name = current.cfg[
"name"];
808 if(current.key ==
"link") {
809 tag_name.erase(0, tag_name.find_last_of(
'/') + 1);
812 tag_names.push_back(tag_name);
817 }
else if(current.key ==
"key") {
818 std::string key_name = current.cfg[
"name"];
820 key_names.push_back(key_name);
827 }
else if(name ==
"wml_schema") {
828 using namespace std::placeholders;
831 missing_types.erase(std::remove_if(missing_types.begin(), missing_types.end(), std::bind(&
reference::match, std::placeholders::_1, std::cref(
defined_types_))), missing_types.end());
837 std::sort(missing_types.begin(), missing_types.end());
838 std::sort(missing_tags.begin(), missing_tags.end());
839 static const config dummy;
840 for(
auto& ref : missing_types) {
841 std::string tag_name;
842 if(ref.tag_ ==
"key") {
849 for(
auto& ref : missing_tags) {
850 std::string tag_name;
851 if(ref.tag_ ==
"tag") {
853 }
else if(ref.tag_ ==
"link") {
869 if(tag_name ==
"key" && name ==
"type" ) {
873 }
else if((tag_name ==
"type" || tag_name ==
"element") && name ==
"link") {
875 }
else if(tag_name ==
"link" && name ==
"name") {
877 std::string link_name =
utils::split(cfg[
"name"].str(),
'/').back();
879 }
else if(tag_name ==
"tag" && name ==
"super") {
887 if(full_path == super) {
917 assert(cfg.has_attribute(
"super"));
920 "super", ref.value_);
927 std::deque<std::string>
path;
928 while(!temp.empty()) {
929 path.push_front(temp.top());
932 if(
path.front() ==
"root") {
940 return std::tie(file_, line_) < std::tie(other.
file_, other.
line_);
945 return with.count(value_) > 0;
951 return root.
find_tag(value_, root, cfg) !=
nullptr;
Used in parsing config file.
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
A config object defines a single node in a WML file, with access to child nodes.
const_all_children_itors all_children_range() const
In-order iteration over all children.
child_itors child_range(config_key_type key)
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
static config_cache & instance()
Get reference to the singleton object.
virtual void open_tag(const std::string &name, const config &parent, int start_line=0, const std::string &file="", bool addition=false) override
Is called when parser opens tag.
virtual void validate(const config &cfg, const std::string &name, int start_line, const std::string &file) override
Validates config.
virtual void close_tag() override
As far as parser is built on stack, some realizations can store stack too.
std::stack< std::string > tag_stack_
std::multimap< std::string, std::string > derivations_
std::vector< reference > referenced_tag_paths_
std::map< std::string, std::string > links_
bool tag_path_exists(const config &cfg, const reference &ref)
schema_derivation_graph_t schema_derivation_graph_
void detect_schema_derivation_cycles()
void print(message_info &message) override
void check_for_duplicates(const std::string &name, std::vector< std::string > &seen, const config &cfg, message_type type, const std::string &file, int line, const std::string &tag)
std::string current_path() const
std::vector< reference > referenced_types_
std::map< std::string, schema_derivation_graph_t::vertex_descriptor > schema_derivation_map_
std::set< std::string > defined_tag_paths_
static bool name_matches(const std::string &pattern, const std::string &name)
std::set< std::string > defined_types_
virtual void validate_key(const config &cfg, const std::string &name, const config_attribute_value &value, int start_line, const std::string &file) override
Checks if key is allowed and if its value is valid What exactly is validated depends on validator rea...
Realization of serialization/validator.hpp abstract validator.
bool read_config_file(const std::string &filename)
Reads config from input.
wml_type::map types_
Type validators.
wml_tag root_
Root of schema information.
std::optional< std::map< std::string, wml_key > > find_mandatory_keys(const wml_tag *tag, const config &cfg) const
Collects all mandatory keys for a tag, including the super keys and overrides.
std::vector< std::string > errors_
virtual void validate(const config &cfg, const std::string &name, int start_line, const std::string &file) override
Validates config.
schema_validator(const std::string &filename, bool validate_schema=false)
Initializes validator from file.
bool create_exceptions_
Controls the way to print errors.
boost::adjacency_list< boost::vecS, boost::vecS, boost::directedS, std::string > link_graph_t
wml_type::ptr find_type(const std::string &type) const
void collect_link_source(link_graph_t &link_graph, link_graph_map_t &link_map, const std::string &type_name, const wml_type *type)
std::stack< message_map > cache_
Caches error messages.
derivation_graph_t derivation_graph_
void queue_message(const config &cfg, T &&... args)
cnt_stack counter_
Contains number of children.
virtual void validate_key(const config &cfg, const std::string &name, const config_attribute_value &value, int start_line, const std::string &file) override
Checks if key is allowed and if its value is valid What exactly is validated depends on validator rea...
void detect_derivation_cycles()
virtual ~schema_validator()
std::map< const wml_type_alias *, link_graph_t::vertex_descriptor > link_graph_map_t
bool config_read_
Shows, if validator is initialized with schema file.
void detect_link_cycles(const std::string &filename)
virtual void close_tag() override
As far as parser is built on stack, some realizations can store stack too.
std::string active_tag_path() const
void validate_mandatory_keys(const wml_tag *tag, const config &cfg, const std::string &name, int start_line, const std::string &file)
Validates that all mandatory keys for a tag are present.
const wml_tag & active_tag() const
void collect_link_target(link_graph_t &link_graph, link_graph_map_t &link_map, const std::string &type_name, const wml_type *type, const wml_type_alias *alias)
std::map< const wml_tag *, derivation_graph_t::vertex_descriptor > derivation_map_
virtual void open_tag(const std::string &name, const config &parent, int start_line=0, const std::string &file="", bool addition=false) override
Is called when parser opens tag.
std::stack< const wml_tag * > stack_
virtual void print(message_info &)
bool have_active_tag() const
wml_key is used to save the information about one key.
const std::string & get_type() const
Stores information about tag.
boost::iterator_range< super_iterator > super(const config &cfg_match) const
int get_min_children() const
const std::string & get_name() const
const wml_tag * find_tag(const std::string &fullpath, const wml_tag &root, const config &match, bool ignore_super=false) const
Returns pointer to tag using full path to it.
int get_max_children() const
boost::iterator_range< key_iterator > keys(const config &cfg_match) const
boost::iterator_range< tag_iterator > tags(const config &cfg_match) const
const wml_key * find_key(const std::string &name, const config &match, bool ignore_super=false) const
Returns pointer to child key.
void expand_all(wml_tag &root)
Calls the expansion on each child.
const std::string & get_super() const
Stores information about a schema type.
const std::string & link() const
Stores information about a schema type.
Stores information about a schema type.
static std::shared_ptr< wml_type > from_config(const config &cfg)
std::shared_ptr< wml_type > ptr
A helper for boost::depth_first_search (DFS) usage with the purpose of detecting cycles.
Declarations for File-IO.
static std::string _(const char *str)
Standard logging facilities (interface).
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
std::string get_wml_location(const std::string &filename, const std::string ¤t_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn't pres...
std::unique_ptr< std::istream > scoped_istream
static void wrong_type_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &type, bool flag_exception)
static std::string missing_super_error(const std::string &file, int line, const std::string &tag, const std::string &schema_name, const std::string &super, bool flag_exception)
static lg::log_domain log_validation("validation")
static void duplicate_key_error(const std::string &file, int line, const std::string &tag, const std::string &pat, const std::string &value, bool flag_exception)
static void duplicate_tag_error(const std::string &file, int line, const std::string &tag, const std::string &pat, const std::string &value, bool flag_exception)
static void print_output(const std::string &message, bool flag_exception=false)
static std::string extra_tag_error(const std::string &file, int line, const std::string &name, int n, const std::string &parent, bool flag_exception)
static std::string extra_key_error(const std::string &file, int line, const std::string &tag, const std::string &key, bool flag_exception)
static std::string at(const std::string &file, int line)
static std::string wrong_value_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &value, const std::string &expected, bool flag_exception)
static std::string missing_key_error(const std::string &file, int line, const std::string &tag, const std::string &key, bool flag_exception)
static std::string wrong_tag_error(const std::string &file, int line, const std::string &name, const std::string &parent, bool flag_exception)
static void inheritance_loop_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &value, int index, bool flag_exception)
static void wrong_path_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &value, bool flag_exception)
static std::string inheritance_cycle_error(const std::string &file, int line, const std::string &tag, const std::string &schema_name, const std::string &value, bool flag_exception)
static std::string missing_tag_error(const std::string &file, int line, const std::string &name, int n, const std::string &parent, bool flag_exception)
static std::string link_cycle_error(const std::string &file, int line, const std::string &tag, const std::string &value, bool flag_exception)
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
bool wildcard_string_match(const std::string &str, const std::string &match)
Match using '*' as any number of characters (including none), '+' as one or more characters,...
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
std::string lineno_string(const std::string &lineno)
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
std::map< std::string, struct preproc_define > preproc_map
One of the realizations of serialization/validator.hpp abstract validator.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Used to manage with not initialized validators Supposed to be thrown from the constructor.
bool match(const std::set< std::string > &with)
bool operator<(const reference &other) const
bool can_find(const wml_tag &root, const config &cfg)
Helper class, don't construct this directly.
static map_location::DIRECTION n
This file contains object "type", which is used to store information about types while annotation par...
bool strict_validation_enabled
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...