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