The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
schema_validator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2011 - 2025
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
29namespace schema_validation
30{
31static 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
37static 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
44static 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
61static 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
75static 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
84static 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
98static 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
113static 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
128static 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
146static 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
160static 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
173static 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
187static 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
199static 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
211static 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
223static 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
236static 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
250
251schema_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 cfg = io::read(*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 = utils::ranges::find(link_map, boost::source(edge, link_graph),
321 [](const auto& link) { return link.second; });
322
323 assert(source != link_map.end());
324
325 const auto target = utils::ranges::find(link_map, boost::target(edge, link_graph),
326 [](const auto& link) { return link.second; });
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
337void 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
351void 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 */
378void 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
435void 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
510utils::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
517utils::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(utils::contains(visited, tag)) {
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
566void 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(utils::contains(visited, tag)) {
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 = utils::ranges::find(derivation_map_, boost::source(edge, derivation_graph_),
596 [](const auto& derivation) { return derivation.second; });
597
598 assert(source != derivation_map_.end());
599
600 const auto target = utils::ranges::find(derivation_map_, boost::target(edge, derivation_graph_),
601 [](const auto& derivation) { return derivation.second; });
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
645wml_type::ptr schema_validator::find_type(const std::string& type) const
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
711void 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") {
716 }
717 if(condition_nesting_ == 0) {
718 if(name == "if" || name == "switch") {
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") {
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
784bool 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
792void 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
805void 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
871void 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
950bool schema_self_validator::reference::match(const std::set<std::string>& with) const
951{
952 return with.count(value_) > 0;
953}
954
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:
979 break;
980 }
981}
982
983} // namespace schema_validation{
static auto & dummy
std::unique_ptr< PangoAttribute, void(*)(PangoAttribute *)> value_
Used in parsing config file.
Definition validator.hpp:38
const std::string name_
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:157
auto all_children_view() const
In-order iteration over all children.
Definition config.hpp:795
child_itors child_range(std::string_view key)
Definition config.cpp:268
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition config.cpp:665
bool has_attribute(std::string_view key) const
Definition config.cpp:157
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::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.
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
int get_min_children() const
Definition tag.hpp:147
const std::string & get_super() const
Definition tag.hpp:157
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:190
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 std::string & get_name() const
Definition tag.hpp:132
boost::iterator_range< super_iterator > super(const config &cfg_match) const
Definition tag.hpp:281
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:120
void expand_all(wml_tag &root)
Calls the expansion on each child.
Definition tag.cpp:283
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.
const config * cfg
Declarations for File-IO.
static std::string _(const char *str)
Definition gettext.hpp:100
Standard logging facilities (interface).
std::unique_ptr< std::istream > scoped_istream
config read(std::istream &in, abstract_validator *validator)
Definition parser.cpp:610
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)
auto find(Container &container, const Value &value, const Projection &projection={})
Definition general.hpp:213
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
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 g
#define e