The Battle for Wesnoth  1.19.0+dev
schema_validator.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2024
3  by Sytyi Nick <nsytyi@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
17 
18 #include "filesystem.hpp"
19 #include "log.hpp"
24 #include "wml_exception.hpp"
25 #include <boost/graph/adjacency_list.hpp>
26 #include <tuple>
27 
28 namespace schema_validation
29 {
30 static lg::log_domain log_validation("validation");
31 
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)
35 
36 static std::string at(const std::string& file, int line)
37 {
38  std::ostringstream ss;
39  ss << line << " " << file;
40  return "at " + ::lineno_string(ss.str());
41 }
42 
43 static void print_output(const std::string& message, bool flag_exception = false)
44 {
45 #ifndef VALIDATION_ERRORS_LOG
46  if(flag_exception) {
47  throw wml_exception("Validation error occurred", message);
48  } else {
49  ERR_VL << message;
50  }
51 #else
52  // dirty hack to avoid "unused" error in case of compiling with definition on
53  flag_exception = true;
54  if(flag_exception) {
55  ERR_VL << message;
56  }
57 #endif
58 }
59 
60 static std::string extra_tag_error(const std::string& file,
61  int line,
62  const std::string& name,
63  int n,
64  const std::string& parent,
65  bool flag_exception)
66 {
67  std::ostringstream ss;
68  ss << "Extra tag [" << name << "]; there may only be " << n << " [" << name << "] in [" << parent << "]\n"
69  << at(file, line) << "\n";
70  print_output(ss.str(), flag_exception);
71  return ss.str();
72 }
73 
74 static std::string wrong_tag_error(
75  const std::string& file, int line, const std::string& name, const std::string& parent, bool flag_exception)
76 {
77  std::ostringstream ss;
78  ss << "Tag [" << name << "] may not be used in [" << parent << "]\n" << at(file, line) << "\n";
79  print_output(ss.str(), flag_exception);
80  return ss.str();
81 }
82 
83 static std::string missing_tag_error(const std::string& file,
84  int line,
85  const std::string& name,
86  int n,
87  const std::string& parent,
88  bool flag_exception)
89 {
90  std::ostringstream ss;
91  ss << "Missing tag [" << name << "]; there must be " << n << " [" << name << "]s in [" << parent << "]\n"
92  << at(file, line) << "\n";
93  print_output(ss.str(), flag_exception);
94  return ss.str();
95 }
96 
97 static std::string extra_key_error(
98  const std::string& file, int line, const std::string& tag, const std::string& key, bool flag_exception)
99 {
100  std::ostringstream ss;
101  ss << "Invalid key '" << key << "='";
102  if(!tag.empty()) {
103  ss << " in tag [" << tag << "]\n";
104  }
105  if(!file.empty()) {
106  ss << at(file, line) << "\n";
107  }
108  print_output(ss.str(), flag_exception);
109  return ss.str();
110 }
111 
112 static std::string missing_key_error(
113  const std::string& file, int line, const std::string& tag, const std::string& key, bool flag_exception)
114 {
115  std::ostringstream ss;
116  ss << "Missing key '" << key << "='";
117  if(!tag.empty()) {
118  ss << " in tag [" << tag << "]\n";
119  }
120  if(!file.empty()) {
121  ss << at(file, line) << "\n";
122  }
123  print_output(ss.str(), flag_exception);
124  return ss.str();
125 }
126 
127 static std::string wrong_value_error(const std::string& file,
128  int line,
129  const std::string& tag,
130  const std::string& key,
131  const std::string& value,
132  const std::string& expected,
133  bool flag_exception)
134 {
135  std::ostringstream ss;
136  ss << "Invalid value '";
137  if(value.length() > 128)
138  ss << value.substr(0, 128) << "...";
139  else ss << value;
140  ss << "' in key '" << key << "=' in tag [" << tag << "]\n" << " (expected value of type " << expected << ") " << at(file, line) << "\n";
141  print_output(ss.str(), flag_exception);
142  return ss.str();
143 }
144 
145 static std::string inheritance_cycle_error(const std::string& file,
146  int line,
147  const std::string& tag,
148  const std::string& schema_name,
149  const std::string& value,
150  bool flag_exception)
151 {
152  std::ostringstream ss;
153  ss << "Inheritance cycle from " << tag << " to " << value << " found\n"
154  << at(file, line) << "\nwith schema " << schema_name << "\n";
155  print_output(ss.str(), flag_exception);
156  return ss.str();
157 }
158 
159 static std::string link_cycle_error(const std::string& file,
160  int line,
161  const std::string& tag,
162  const std::string& value,
163  bool flag_exception)
164 {
165  std::ostringstream ss;
166  ss << "Link cycle from " << tag << " to " << value << " found\n"
167  << at(file, line);
168  print_output(ss.str(), flag_exception);
169  return ss.str();
170 }
171 
172 static std::string missing_super_error(const std::string& file,
173  int line,
174  const std::string& tag,
175  const std::string& schema_name,
176  const std::string& super,
177  bool flag_exception)
178 {
179  std::ostringstream ss;
180  ss << "Super " << super << " not found. Needed by " << tag << "\n"
181  << at(file, line) << "\nwith schema " << schema_name << "\n";
182  print_output(ss.str(), flag_exception);
183  return ss.str();
184 }
185 
186 static void wrong_path_error(const std::string& file,
187  int line,
188  const std::string& tag,
189  const std::string& key,
190  const std::string& value,
191  bool flag_exception)
192 {
193  std::ostringstream ss;
194  ss << "Unknown path reference '" << value << "' in key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
195  print_output(ss.str(), flag_exception);
196 }
197 
198 static void duplicate_tag_error(const std::string& file,
199  int line,
200  const std::string& tag,
201  const std::string& pat,
202  const std::string& value,
203  bool flag_exception)
204 {
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";
207  print_output(ss.str(), flag_exception);
208 }
209 
210 static void duplicate_key_error(const std::string& file,
211  int line,
212  const std::string& tag,
213  const std::string& pat,
214  const std::string& value,
215  bool flag_exception)
216 {
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";
219  print_output(ss.str(), flag_exception);
220 }
221 
222 static void inheritance_loop_error(const std::string& file,
223  int line,
224  const std::string& tag,
225  const std::string& key,
226  const std::string& value,
227  int index,
228  bool flag_exception)
229 {
230  std::ostringstream ss;
231  ss << "Inheritance loop " << key << "=" << value << " found (at offset " << index << ") in tag [" << tag << "]\n" << at(file, line) << "\n";
232  print_output(ss.str(), flag_exception);
233 }
234 
235 static void wrong_type_error(const std::string & file, int line,
236  const std::string & tag,
237  const std::string & key,
238  const std::string & type,
239  bool flag_exception)
240 {
241  std::ostringstream ss;
242  ss << "Invalid type '" << type << "' in key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
243  print_output(ss.str(), flag_exception);
244 }
245 
247 {
248 }
249 
250 schema_validator::schema_validator(const std::string& config_file_name, bool validate_schema)
251  : abstract_validator(config_file_name)
252  , create_exceptions_(strict_validation_enabled)
253  , config_read_(false)
254  , validate_schema_(validate_schema)
255  , errors_()
256 {
257  if(!read_config_file(config_file_name)) {
258  ERR_VL << "Schema file " << config_file_name << " was not read.";
259  throw abstract_validator::error("Schema file " + config_file_name + " was not read.\n");
260  } else {
261  stack_.push(&root_);
262  counter_.emplace();
263  cache_.emplace();
265  LOG_VL << "Schema file " << config_file_name << " was read.";
266  LOG_VL << "Validator initialized";
267  }
268 }
269 
270 bool schema_validator::read_config_file(const std::string& filename)
271 {
272  config cfg;
273  try {
274  std::unique_ptr<abstract_validator> validator;
275  if(validate_schema_) {
276  validator.reset(new schema_self_validator());
277  }
278  preproc_map preproc(game_config::config_cache::instance().get_preproc_map());
279  filesystem::scoped_istream stream = preprocess_file(filename, &preproc);
280  read(cfg, *stream, validator.get());
281  } catch(const config::error& e) {
282  ERR_VL << "Failed to read file " << filename << ":\n" << e.what();
283  return false;
284  }
285 
286  for(const config& g : cfg.child_range("wml_schema")) {
287  for(const config& schema : g.child_range("tag")) {
288  if(schema["name"].str() == "root") {
289  //@NOTE Don't know, maybe merging of roots needed.
290  root_ = wml_tag(schema);
291  }
292  }
293  types_["t_string"] = std::make_shared<wml_type_tstring>();
294  for(const config& type : g.child_range("type")) {
295  try {
296  types_[type["name"].str()] = wml_type::from_config(type);
297  } catch(const std::exception&) {
298  // Need to check all type values in schema-generator
299  }
300  }
301  }
302 
303  detect_link_cycles(filename);
304 
305  config_read_ = true;
306  return true;
307 }
308 
309 void schema_validator::detect_link_cycles(const std::string& filename) {
310  link_graph_t link_graph;
311  link_graph_map_t link_map;
312 
313  for (auto [type_name, type] : types_) {
314  collect_link_source(link_graph, link_map, type_name, type.get());
315  }
316 
317  boost::depth_first_search(link_graph,
318  boost::visitor(utils::back_edge_detector([&](const link_graph_t::edge_descriptor edge) {
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); });
321 
322  assert(source != link_map.end());
323 
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); });
326 
327  assert(target != link_map.end());
328 
329  const auto& alias_type = link_graph[source->second];
330  const auto& link_type = link_graph[target->second];
331 
332  throw abstract_validator::error(link_cycle_error(filename, 0, alias_type, link_type, false));
333  })));
334 }
335 
336 void schema_validator::collect_link_source(link_graph_t& link_graph, link_graph_map_t& link_map, const std::string& type_name, const wml_type* type) {
337  if (auto alias = dynamic_cast<const wml_type_alias*>(type)) {
338  auto it = types_.find(alias->link());
339 
340  if (it != types_.end()) {
341  collect_link_target(link_graph, link_map, alias->link(), it->second.get(), alias);
342  }
343  } else if (auto composite = dynamic_cast<const wml_type_composite*>(type)) {
344  for(auto elem : composite->subtypes()) {
345  collect_link_source(link_graph, link_map, type_name, elem.get());
346  }
347  }
348 }
349 
350 void schema_validator::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) {
351  if (auto link = dynamic_cast<const wml_type_alias*>(type)) {
352  if (link_map.find(alias) == link_map.end()) {
353  link_map.emplace(
354  alias,
355  boost::add_vertex(type_name, link_graph));
356  }
357 
358  if (link_map.find(link) == link_map.end()) {
359  link_map.emplace(
360  link,
361  boost::add_vertex(alias->link(), link_graph));
362  }
363 
364  boost::add_edge(link_map[alias], link_map[link], link_graph);
365  } else if (auto composite = dynamic_cast<const wml_type_composite*>(type)) {
366  for(auto elem : composite->subtypes()) {
367  collect_link_target(link_graph, link_map, type_name, elem.get(), alias);
368  }
369  }
370 }
371 
372 /*
373  * Please, @Note that there is some magic in pushing and poping to/from stacks.
374  * assume they all are on their place due to parser algorithm
375  * and validation logic
376  */
377 void schema_validator::open_tag(const std::string& name, const config& parent, int start_line, const std::string& file, bool addition)
378 {
379  if(name.empty()) {
380  // Opened the root tag; nothing special to do here
381  } else if(!stack_.empty()) {
382  const wml_tag* tag = nullptr;
383 
384  if(stack_.top()) {
385  tag = active_tag().find_tag(name, root_, parent);
386 
387  if(!tag) {
388  errors_.emplace_back(wrong_tag_error(file, start_line, name, stack_.top()->get_name(), create_exceptions_));
389  } else {
390  if(!addition) {
391  counter& cnt = counter_.top()[name];
392  ++cnt.cnt;
393  counter& total_cnt = counter_.top()[""];
394  ++total_cnt.cnt;
395  }
396  }
397  }
398 
399  stack_.push(tag);
400  } else {
401  stack_.push(nullptr);
402  }
403 
404  counter_.emplace();
405  cache_.emplace();
406 }
407 
409 {
410  stack_.pop();
411  counter_.pop();
412  // cache_ is normally cleared in another place.
413  // However, if we're closing the root tag, clear it now
414  if(stack_.empty()) {
415  print_cache();
416  }
417 }
418 
420 {
421  for(auto& m : cache_.top()) {
422  for(auto& list : m.second) {
423  print(list);
424  }
425  }
426 
427  cache_.pop();
428 }
429 
430 void schema_validator::validate(const config& cfg, const std::string& name, int start_line, const std::string& file)
431 {
432  // close previous errors and print them to output.
433  print_cache();
434 
435  // clear cache
436  auto cache_it = cache_.top().find(&cfg);
437  if(cache_it != cache_.top().end()) {
438  cache_it->second.clear();
439  }
440 
441  // Please note that validating unknown tag keys the result will be false
442  // Checking all elements counters.
443  if(have_active_tag() && is_valid()) {
444  const wml_tag& active = active_tag();
445 
446  if(&active == &root_) {
448  } else {
449  // Build derivation graph
450  const auto super_tags = active.super(cfg);
451 
452  for(const auto& [super_path, super_tag] : super_tags) {
453  if(derivation_map_.find(&active) == derivation_map_.end()) {
454  derivation_map_.emplace(
455  &active, boost::add_vertex({&active, active_tag_path()}, derivation_graph_));
456  }
457 
458  if(derivation_map_.find(super_tag) == derivation_map_.end()) {
459  derivation_map_.emplace(super_tag, boost::add_vertex({super_tag, super_path}, derivation_graph_));
460  }
461 
462  boost::add_edge(
463  derivation_map_[&active], derivation_map_[super_tag], {cfg, file, start_line}, derivation_graph_);
464  }
465 
466  // Report missing super
467  const auto super_expected = utils::split(active.get_super());
468 
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; });
472 
473  if(!super_exists) {
474  queue_message(cfg, MISSING_SUPER, file, start_line, 0, active_tag_path(), name_, expected);
475  }
476  }
477  }
478 
479  for(const auto& tag : active.tags(cfg)) {
480  int cnt = counter_.top()[tag.first].cnt;
481 
482  if(tag.second.get_min() > cnt) {
483  queue_message(cfg, MISSING_TAG, file, start_line, tag.second.get_min(), tag.first, "", name);
484  continue;
485  }
486 
487  if(tag.second.get_max() < cnt) {
488  queue_message(cfg, EXTRA_TAG, file, start_line, tag.second.get_max(), tag.first, "", name);
489  }
490  }
491 
492  int total_cnt = counter_.top()[""].cnt;
493  if(active.get_min_children() > total_cnt) {
494  queue_message(cfg, MISSING_TAG, file, start_line, active.get_min_children(), "*", "", active.get_name());
495  } else if(active_tag().get_max_children() < total_cnt) {
496  queue_message(cfg, EXTRA_TAG, file, start_line, active.get_max_children(), "*", "", active.get_name());
497  }
498 
499  validate_mandatory_keys(&active, cfg, name, start_line, file);
500  }
501 }
502 
503 std::optional<std::map<std::string, wml_key>> schema_validator::find_mandatory_keys(
504  const wml_tag* tag, const config& cfg) const
505 {
506  auto visited = std::vector<const wml_tag*>();
507  return find_mandatory_keys(tag, cfg, visited);
508 }
509 
510 std::optional<std::map<std::string, wml_key>> schema_validator::find_mandatory_keys(
511  const wml_tag* tag, const config& cfg, std::vector<const wml_tag*>& visited) const
512 {
513  // Return an empty optional if a super cycle is detected.
514  if(std::find(visited.begin(), visited.end(), tag) != visited.end()) {
515  return std::nullopt;
516  }
517 
518  visited.push_back(tag);
519 
520  auto mandatory_keys = std::map<std::string, wml_key>();
521 
522  // Override super mandatory keys for each level from the highest one first.
523  for(const auto& [_, super_tag] : tag->super(cfg)) {
524  auto super_mandatory_keys = find_mandatory_keys(super_tag, cfg, visited);
525 
526  // Return an empty optional if a super cycle is detected.
527  if(!super_mandatory_keys) {
528  return std::nullopt;
529  }
530 
531  super_mandatory_keys->merge(mandatory_keys);
532  mandatory_keys.swap(*super_mandatory_keys);
533  }
534 
535  // Set or override the mandatory keys on the lowest level (the key itself).
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;
539  }
540  }
541 
542  return mandatory_keys;
543 }
544 
546  const wml_tag* tag, const config& cfg, const std::string& name, int start_line, const std::string& file)
547 {
548  const auto mandatory_keys = find_mandatory_keys(tag, cfg);
549 
550  // Skip validation if a super cycle is detected.
551  if(!mandatory_keys) {
552  return;
553  }
554 
555  auto visited = std::vector<const wml_tag*>();
556  return validate_mandatory_keys(*mandatory_keys, tag, cfg, name, start_line, file, visited);
557 }
558 
559 void schema_validator::validate_mandatory_keys(const std::map<std::string, wml_key>& mandatory_keys,
560  const wml_tag* tag,
561  const config& cfg,
562  const std::string& name,
563  int start_line,
564  const std::string& file,
565  std::vector<const wml_tag*>& visited)
566 {
567  // Skip validation if a super cycle is detected.
568  if(std::find(visited.begin(), visited.end(), tag) != visited.end()) {
569  return;
570  }
571 
572  visited.push_back(tag);
573 
574  // Checking if all mandatory keys are present
575  for(const auto& key : mandatory_keys) {
576  if(key.second.is_mandatory()) {
577  if(cfg.get(key.first) == nullptr) {
578  queue_message(cfg, MISSING_KEY, file, start_line, 0, name, key.first);
579  }
580  }
581  }
582 }
583 
585 {
586  boost::depth_first_search(derivation_graph_,
587  boost::visitor(utils::back_edge_detector([&](const derivation_graph_t::edge_descriptor edge) {
588  const auto source = std::find_if(derivation_map_.begin(), derivation_map_.end(),
589  [&](const auto& derivation) { return derivation.second == boost::source(edge, derivation_graph_); });
590 
591  assert(source != derivation_map_.end());
592 
593  const auto target = std::find_if(derivation_map_.begin(), derivation_map_.end(),
594  [&](const auto& derivation) { return derivation.second == boost::target(edge, derivation_graph_); });
595 
596  assert(target != derivation_map_.end());
597 
598  const auto& tag_path = derivation_graph_[source->second].second;
599  const auto& super_path = derivation_graph_[target->second].second;
600 
601  const auto& [cfg, file, line] = derivation_graph_[edge];
602 
603  queue_message(cfg, SUPER_CYCLE, file, line, 0, tag_path, name_, super_path);
604  })));
605 }
606 
608  const config& cfg, const std::string& name, const config_attribute_value& value, int start_line, const std::string& file)
609 {
610  if(have_active_tag() && !active_tag().get_name().empty() && is_valid()) {
611  // checking existing keys
612  const wml_key* key = active_tag().find_key(name, cfg);
613  if(key) {
614  bool matched = false;
615  for(auto& possible_type : utils::split(key->get_type())) {
616  if(auto type = find_type(possible_type)) {
617  if(type->matches(value, types_)) {
618  matched = true;
619  break;
620  }
621  }
622  }
623  if(!matched) {
624  queue_message(cfg, WRONG_VALUE, file, start_line, 0, active_tag().get_name(), name, value, key->get_type());
625  }
626  } else {
627  queue_message(cfg, EXTRA_KEY, file, start_line, 0, active_tag().get_name(), name);
628  }
629  }
630 }
631 
633 {
634  assert(have_active_tag() && "Tried to get active tag name when there was none");
635  return *stack_.top();
636 }
637 
639 {
640  auto it = types_.find(type);
641  if(it == types_.end()) {
642  return nullptr;
643  }
644  return it->second;
645 }
646 
648 {
649  return !stack_.empty() && stack_.top();
650 }
651 
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());
657  temp.pop();
658  }
659  if(path.front() == "root") {
660  path.pop_front();
661  }
662  return utils::join(path, "/");
663 }
664 
666 {
667  switch(el.type) {
668  case WRONG_TAG:
669  errors_.emplace_back(wrong_tag_error(el.file, el.line, el.tag, el.value, create_exceptions_));
670  break;
671  case EXTRA_TAG:
672  errors_.emplace_back(extra_tag_error(el.file, el.line, el.tag, el.n, el.value, create_exceptions_));
673  break;
674  case MISSING_TAG:
675  errors_.emplace_back(missing_tag_error(el.file, el.line, el.tag, el.n, el.value, create_exceptions_));
676  break;
677  case EXTRA_KEY:
678  errors_.emplace_back(extra_key_error(el.file, el.line, el.tag, el.key, create_exceptions_));
679  break;
680  case WRONG_VALUE:
681  errors_.emplace_back(wrong_value_error(el.file, el.line, el.tag, el.key, el.value, el.expected, create_exceptions_));
682  break;
683  case MISSING_KEY:
684  errors_.emplace_back(missing_key_error(el.file, el.line, el.tag, el.key, create_exceptions_));
685  break;
686  case MISSING_SUPER:
687  errors_.emplace_back(missing_super_error(el.file, el.line, el.tag, el.key, el.value, create_exceptions_));
688  break;
689  case SUPER_CYCLE:
690  errors_.emplace_back(inheritance_cycle_error(el.file, el.line, el.tag, el.key, el.value, create_exceptions_));
691  break;
692  }
693 }
694 
696  : schema_validator(filesystem::get_wml_location("schema/schema.cfg"), false)
697  , type_nesting_()
698  , condition_nesting_()
699 {
700  defined_types_.insert("t_string");
701 }
702 
703 
704 void schema_self_validator::open_tag(const std::string& name, const config& parent, int start_line, const std::string& file, bool addition)
705 {
706  schema_validator::open_tag(name, parent, start_line, file, addition);
707  if(name == "type") {
708  type_nesting_++;
709  }
710  if(condition_nesting_ == 0) {
711  if(name == "if" || name == "switch") {
712  condition_nesting_ = 1;
713  } else if(name == "tag") {
714  tag_stack_.emplace();
715  }
716  } else {
718  }
719 }
720 
722 {
723  if(have_active_tag()) {
724  auto tag_name = active_tag().get_name();
725  if(tag_name == "type") {
726  type_nesting_--;
727  } else if(condition_nesting_ == 0 && tag_name == "tag") {
728  tag_stack_.pop();
729  }
730  }
731  if(condition_nesting_ > 0) {
733  }
735 }
736 
738  std::vector<std::string> path = utils::split(ref.value_, '/');
739  std::string suffix = path.back();
740  path.pop_back();
741  while(!path.empty()) {
742  std::string prefix = utils::join(path, "/");
743  auto link = links_.find(prefix);
744  if(link != links_.end()) {
745  std::string new_path = link->second + "/" + suffix;
746  if(defined_tag_paths_.count(new_path) > 0) {
747  return true;
748  }
749  path = utils::split(new_path, '/');
750  suffix = path.back();
751  //suffix = link->second + "/" + suffix;
752  } else {
753  const auto supers = derivations_.equal_range(prefix);
754  if(supers.first != supers.second) {
755  reference super_ref = ref;
756  for(auto cur = supers.first ; cur != supers.second; ++cur) {
757  super_ref.value_ = cur->second + "/" + suffix;
758  if(super_ref.value_.find(ref.value_) == 0) {
759  continue;
760  }
761  if(tag_path_exists(cfg, super_ref)) {
762  return true;
763  }
764  }
765  }
766  std::string new_path = prefix + "/" + suffix;
767  if(defined_tag_paths_.count(new_path) > 0) {
768  return true;
769  }
770  suffix = path.back() + "/" + suffix;
771  }
772  path.pop_back();
773  }
774  return false;
775 }
776 
777 bool schema_self_validator::name_matches(const std::string& pattern, const std::string& name)
778 {
779  for(const std::string& pat : utils::split(pattern)) {
780  if(utils::wildcard_string_match(name, pat)) return true;
781  }
782  return false;
783 }
784 
785 void schema_self_validator::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) {
786  auto split = utils::split(name);
787  for(const std::string& pattern : seen) {
788  for(const std::string& key : split) {
789  if(name_matches(pattern, key)) {
790  queue_message(cfg, type, file, line, 0, tag, pattern, name);
791  continue;
792  }
793  }
794  }
795  seen.push_back(name);
796 }
797 
798 void schema_self_validator::validate(const config& cfg, const std::string& name, int start_line, const std::string& file)
799 {
800  if(type_nesting_ == 1 && name == "type") {
801  defined_types_.insert(cfg["name"]);
802  } else if(name == "tag") {
803  bool first_tag = true, first_key = true;
804  std::vector<std::string> tag_names, key_names;
805  for(auto current : cfg.all_children_range()) {
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);
810  }
811  if(first_tag) {
812  tag_names.push_back(tag_name);
813  first_tag = false;
814  continue;
815  }
816  check_for_duplicates(tag_name, tag_names, current.cfg, DUPLICATE_TAG, file, start_line, current.key);
817  } else if(current.key == "key") {
818  std::string key_name = current.cfg["name"];
819  if(first_key) {
820  key_names.push_back(key_name);
821  first_key = false;
822  continue;
823  }
824  check_for_duplicates(key_name, key_names, current.cfg, DUPLICATE_KEY, file, start_line, current.key);
825  }
826  }
827  } else if(name == "wml_schema") {
828  using namespace std::placeholders;
829  std::vector<reference> missing_types = referenced_types_, missing_tags = referenced_tag_paths_;
830  // Remove all the known types
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());
832  // Remove all the known tags. This is more complicated since links behave similar to a symbolic link.
833  // In other words, the presence of links means there may be more than one way to refer to a given tag.
834  // But that's not all! It's possible to refer to a tag through a derived tag even if it's actually defined in the base tag.
835  auto end = std::remove_if(missing_tags.begin(), missing_tags.end(), std::bind(&reference::match, std::placeholders::_1, std::cref(defined_tag_paths_)));
836  missing_tags.erase(std::remove_if(missing_tags.begin(), end, std::bind(&schema_self_validator::tag_path_exists, this, std::ref(cfg), std::placeholders::_1)), missing_tags.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") {
843  tag_name = "type";
844  } else {
845  tag_name = "link";
846  }
847  queue_message(dummy, WRONG_TYPE, ref.file_, ref.line_, 0, ref.tag_, tag_name, ref.value_);
848  }
849  for(auto& ref : missing_tags) {
850  std::string tag_name;
851  if(ref.tag_ == "tag") {
852  tag_name = "super";
853  } else if(ref.tag_ == "link") {
854  tag_name = "name";
855  }
856  queue_message(dummy, WRONG_PATH, ref.file_, ref.line_, 0, ref.tag_, tag_name, ref.value_);
857  }
858 
860  }
861  schema_validator::validate(cfg, name, start_line, file);
862 }
863 
864 void schema_self_validator::validate_key(const config& cfg, const std::string& name, const config_attribute_value& value, int start_line, const std::string& file)
865 {
866  schema_validator::validate_key(cfg, name, value, start_line, file);
867  if(have_active_tag() && !active_tag().get_name().empty() && is_valid()) {
868  const std::string& tag_name = active_tag().get_name();
869  if(tag_name == "key" && name == "type" ) {
870  for(auto& possible_type : utils::split(cfg["type"])) {
871  referenced_types_.emplace_back(possible_type, file, start_line, tag_name);
872  }
873  } else if((tag_name == "type" || tag_name == "element") && name == "link") {
874  referenced_types_.emplace_back(cfg["link"], file, start_line, tag_name);
875  } else if(tag_name == "link" && name == "name") {
876  referenced_tag_paths_.emplace_back(cfg["name"], file, start_line, tag_name);
877  std::string link_name = utils::split(cfg["name"].str(), '/').back();
878  links_.emplace(current_path() + "/" + link_name, cfg["name"]);
879  } else if(tag_name == "tag" && name == "super") {
880  for(auto super : utils::split(cfg["super"])) {
881  const auto full_path = current_path();
882 
883  const auto& ref = referenced_tag_paths_.emplace_back(super, file, start_line, tag_name);
884  if(condition_nesting_ > 0) {
885  continue;
886  }
887  if(full_path == super) {
888  queue_message(cfg, SUPER_LOOP, file, start_line, cfg["super"].str().find(super), tag_name, "super", super);
889  continue;
890  }
891  derivations_.emplace(full_path, super);
892 
893  // Build derivation graph
894  if(schema_derivation_map_.find(full_path) == schema_derivation_map_.end()) {
895  schema_derivation_map_.emplace(full_path, boost::add_vertex(full_path, schema_derivation_graph_));
896  }
897 
898  if(schema_derivation_map_.find(super) == schema_derivation_map_.end()) {
899  schema_derivation_map_.emplace(super, boost::add_vertex(super, schema_derivation_graph_));
900  }
901 
902  boost::add_edge(schema_derivation_map_[full_path], schema_derivation_map_[super], {cfg, ref},
904  }
905  } else if(condition_nesting_ == 0 && tag_name == "tag" && name == "name") {
906  tag_stack_.top() = value.str();
908  }
909  }
910 }
911 
913 {
914  boost::depth_first_search(schema_derivation_graph_,
915  boost::visitor(utils::back_edge_detector([&](const schema_derivation_graph_t::edge_descriptor edge) {
916  const auto& [cfg, ref] = schema_derivation_graph_[edge];
917  assert(cfg.has_attribute("super"));
918 
919  queue_message(cfg, SUPER_LOOP, ref.file_, ref.line_, cfg.get("super")->str().find(ref.value_), ref.tag_,
920  "super", ref.value_);
921  })));
922 }
923 
925 {
926  std::stack<std::string> temp = tag_stack_;
927  std::deque<std::string> path;
928  while(!temp.empty()) {
929  path.push_front(temp.top());
930  temp.pop();
931  }
932  if(path.front() == "root") {
933  path.pop_front();
934  }
935  return utils::join(path, "/");
936 }
937 
939 {
940  return std::tie(file_, line_) < std::tie(other.file_, other.line_);
941 }
942 
943 bool schema_self_validator::reference::match(const std::set<std::string>& with)
944 {
945  return with.count(value_) > 0;
946 }
947 
949 {
950  // The problem is that the schema being validated is that of the schema!!!
951  return root.find_tag(value_, root, cfg) != nullptr;
952 }
953 
955 {
957  switch(el.type) {
958  case WRONG_TYPE:
960  break;
961  case WRONG_PATH:
963  break;
964  case DUPLICATE_TAG:
966  break;
967  case DUPLICATE_KEY:
969  break;
970  case SUPER_LOOP:
971  inheritance_loop_error(el.file, el.line, el.tag, el.key, el.value, el.n, create_exceptions_);
972  break;
973  }
974 }
975 
976 } // namespace schema_validation{
double g
Definition: astarsearch.cpp:63
Used in parsing config file.
Definition: validator.hpp:38
const std::string name_
Definition: validator.hpp:101
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.
Definition: config.hpp:159
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:887
child_itors child_range(config_key_type key)
Definition: config.cpp:273
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.
Definition: config.cpp:687
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::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 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::map< std::string, schema_derivation_graph_t::vertex_descriptor > schema_derivation_map_
static bool name_matches(const std::string &pattern, const std::string &name)
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.
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...
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.
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.
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 &)
wml_key is used to save the information about one key.
Definition: key.hpp:37
const std::string & get_type() const
Definition: key.hpp:64
Stores information about tag.
Definition: tag.hpp:48
boost::iterator_range< super_iterator > super(const config &cfg_match) const
Definition: tag.hpp:311
int get_min_children() const
Definition: tag.hpp:177
const std::string & get_name() const
Definition: tag.hpp:162
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.
Definition: tag.cpp:179
int get_max_children() const
Definition: tag.hpp:182
boost::iterator_range< key_iterator > keys(const config &cfg_match) const
Definition: tag.hpp:306
boost::iterator_range< tag_iterator > tags(const config &cfg_match) const
Definition: tag.hpp:301
const wml_key * find_key(const std::string &name, const config &match, bool ignore_super=false) const
Returns pointer to child key.
Definition: tag.cpp:109
void expand_all(wml_tag &root)
Calls the expansion on each child.
Definition: tag.cpp:272
const std::string & get_super() const
Definition: tag.hpp:187
Stores information about a schema type.
Definition: type.hpp:66
const std::string & link() const
Definition: type.hpp:72
Stores information about a schema type.
Definition: type.hpp:79
Stores information about a schema type.
Definition: type.hpp:36
static std::shared_ptr< wml_type > from_config(const config &cfg)
Definition: type.cpp:53
std::shared_ptr< wml_type > ptr
Definition: type.hpp:42
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)
Definition: gettext.hpp:93
Standard logging facilities (interface).
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:180
std::string get_wml_location(const std::string &filename, const std::string &current_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
Definition: filesystem.hpp:50
std::string path
Definition: filesystem.cpp:84
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.
Definition: unicode.cpp:70
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
#define ERR_VL
#define LOG_VL
One of the realizations of serialization/validator.hpp abstract validator.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
Used to manage with not initialized validators Supposed to be thrown from the constructor.
Definition: validator.hpp:97
bool match(const std::set< std::string > &with)
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
Definition: validator.cpp:21
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e