The Battle for Wesnoth  1.15.0-dev
schema_validator.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2018 by Sytyi Nick <nsytyi@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "filesystem.hpp"
18 #include "gettext.hpp"
19 #include "log.hpp"
21 #include "wml_exception.hpp"
22 
24 {
25 static lg::log_domain log_validation("validation");
26 
27 #define ERR_VL LOG_STREAM(err, log_validation)
28 #define WRN_VL LOG_STREAM(warn, log_validation)
29 #define LOG_VL LOG_STREAM(info, log_validation)
30 
31 static std::string at(const std::string& file, int line)
32 {
33  std::ostringstream ss;
34  ss << line << " " << file;
35  return "at " + ::lineno_string(ss.str());
36 }
37 
38 static void print_output(const std::string& message, bool flag_exception = false)
39 {
40 #ifndef VALIDATION_ERRORS_LOG
41  if(flag_exception) {
42  throw wml_exception("Validation error occurred", message);
43  } else {
44  ERR_VL << message;
45  }
46 #else
47  // dirty hack to avoid "unused" error in case of compiling with definition on
48  flag_exception = true;
49  if(flag_exception) {
50  ERR_VL << message;
51  }
52 #endif
53 }
54 
55 static void extra_tag_error(const std::string& file,
56  int line,
57  const std::string& name,
58  int n,
59  const std::string& parent,
60  bool flag_exception)
61 {
62  std::ostringstream ss;
63  ss << "Extra tag [" << name << "]; there may only be " << n << " [" << name << "] in [" << parent << "]\n"
64  << at(file, line) << "\n";
65  print_output(ss.str(), flag_exception);
66 }
67 
68 static void wrong_tag_error(
69  const std::string& file, int line, const std::string& name, const std::string& parent, bool flag_exception)
70 {
71  std::ostringstream ss;
72  ss << "Tag [" << name << "] may not be used in [" << parent << "]\n" << at(file, line) << "\n";
73  print_output(ss.str(), flag_exception);
74 }
75 
76 static void missing_tag_error(const std::string& file,
77  int line,
78  const std::string& name,
79  int n,
80  const std::string& parent,
81  bool flag_exception)
82 {
83  std::ostringstream ss;
84  ss << "Missing tag [" << name << "]; there must be " << n << " [" << name << "]s in [" << parent << "]\n"
85  << at(file, line) << "\n";
86  print_output(ss.str(), flag_exception);
87 }
88 
89 static void extra_key_error(
90  const std::string& file, int line, const std::string& tag, const std::string& key, bool flag_exception)
91 {
92  std::ostringstream ss;
93  ss << "Invalid key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
94  print_output(ss.str(), flag_exception);
95 }
96 
97 static void missing_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 << "Missing key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
102  print_output(ss.str(), flag_exception);
103 }
104 
105 static void wrong_value_error(const std::string& file,
106  int line,
107  const std::string& tag,
108  const std::string& key,
109  const std::string& value,
110  bool flag_exception)
111 {
112  std::ostringstream ss;
113  ss << "Invalid value '" << value << "' in key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
114 
115  print_output(ss.str(), flag_exception);
116 }
117 
119 {
120 }
121 
122 schema_validator::schema_validator(const std::string& config_file_name)
123  : config_read_(false)
125  , root_()
126  , stack_()
127  , counter_()
128  , cache_()
129  , types_()
130 {
131  if(!read_config_file(config_file_name)) {
132  ERR_VL << "Schema file " << config_file_name << " was not read." << std::endl;
133  throw abstract_validator::error("Schema file " + config_file_name + " was not read.\n");
134  } else {
135  stack_.push(&root_);
136  counter_.emplace();
137  cache_.emplace();
139  LOG_VL << "Schema file " << config_file_name << " was read.\n"
140  << "Validator initialized\n";
141  }
142 }
143 
144 bool schema_validator::read_config_file(const std::string& filename)
145 {
146  config cfg;
147  try {
148  preproc_map preproc(game_config::config_cache::instance().get_preproc_map());
149  filesystem::scoped_istream stream = preprocess_file(filename, &preproc);
150  read(cfg, *stream);
151  } catch(const config::error& e) {
152  ERR_VL << "Failed to read file " << filename << ":\n" << e.what() << "\n";
153  return false;
154  }
155 
156  for(const config& g : cfg.child_range("wml_schema")) {
157  for(const config& schema : g.child_range("tag")) {
158  if(schema["name"].str() == "root") {
159  //@NOTE Don't know, maybe merging of roots needed.
160  root_ = class_tag(schema);
161  }
162  }
163 
164  for(const config& type : g.child_range("type")) {
165  try {
166  types_[type["name"].str()] = boost::regex(type["value"].str());
167  } catch(const std::exception&) {
168  // Need to check all type values in schema-generator
169  }
170  }
171  }
172 
173  config_read_ = true;
174  return true;
175 }
176 
177 /*
178  * Please, @Note that there is some magic in pushing and poping to/from stacks.
179  * assume they all are on their place due to parser algorithm
180  * and validation logic
181  */
182 void schema_validator::open_tag(const std::string& name, int start_line, const std::string& file, bool addittion)
183 {
184  if(!stack_.empty()) {
185  const class_tag* tag = nullptr;
186 
187  if(stack_.top()) {
188  tag = stack_.top()->find_tag(name, root_);
189 
190  if(!tag) {
191  wrong_tag_error(file, start_line, name, stack_.top()->get_name(), create_exceptions_);
192  } else {
193  if(!addittion) {
194  counter& cnt = counter_.top()[name];
195  ++cnt.cnt;
196  }
197  }
198  }
199 
200  stack_.push(tag);
201  } else {
202  stack_.push(nullptr);
203  }
204 
205  counter_.emplace();
206  cache_.emplace();
207 }
208 
210 {
211  stack_.pop();
212  counter_.pop();
213  // cache_ is cleared in another place.
214 }
215 
216 void schema_validator::validate(const config& cfg, const std::string& name, int start_line, const std::string& file)
217 {
218  // close previous errors and print them to output.
219  for(auto& m : cache_.top()) {
220  for(auto& list : m.second) {
221  print(list);
222  }
223  }
224 
225  cache_.pop();
226 
227  // clear cache
228  auto cache_it = cache_.top().find(&cfg);
229  if(cache_it != cache_.top().end()) {
230  cache_it->second.clear();
231  }
232 
233  // Please note that validating unknown tag keys the result will be false
234  // Checking all elements counters.
235  if(!stack_.empty() && stack_.top() && config_read_) {
236  for(const auto& tag : stack_.top()->tags()) {
237  int cnt = counter_.top()[tag.first].cnt;
238 
239  if(tag.second.get_min() > cnt) {
240  cache_.top()[&cfg].emplace_back(
241  MISSING_TAG, file, start_line, tag.second.get_min(), tag.first, "", name);
242  continue;
243  }
244 
245  if(tag.second.get_max() < cnt) {
246  cache_.top()[&cfg].emplace_back(
247  EXTRA_TAG, file, start_line, tag.second.get_max(), tag.first, "", name);
248  }
249  }
250 
251  // Checking if all mandatory keys are present
252  for(const auto& key : stack_.top()->keys()) {
253  if(key.second.is_mandatory()) {
254  if(cfg.get(key.first) == nullptr) {
255  cache_.top()[&cfg].emplace_back(MISSING_KEY, file, start_line, 0, name, key.first);
256  }
257  }
258  }
259  }
260 }
261 
263  const config& cfg, const std::string& name, const std::string& value, int start_line, const std::string& file)
264 {
265  if(!stack_.empty() && stack_.top() && config_read_) {
266  // checking existing keys
267  const class_key* key = stack_.top()->find_key(name);
268  if(key) {
269  auto itt = types_.find(key->get_type());
270 
271  if(itt != types_.end()) {
272  boost::smatch sub;
273  bool res = boost::regex_match(value, sub, itt->second);
274 
275  if(!res) {
276  cache_.top()[&cfg].emplace_back(
277  WRONG_VALUE, file, start_line, 0, stack_.top()->get_name(), name, value);
278  }
279  }
280  } else {
281  cache_.top()[&cfg].emplace_back(EXTRA_KEY, file, start_line, 0, stack_.top()->get_name(), name);
282  }
283  }
284 }
285 
287 {
288  switch(el.type) {
289  case WRONG_TAG:
291  break;
292  case EXTRA_TAG:
293  extra_tag_error(el.file, el.line, el.tag, el.n, el.value, create_exceptions_);
294  break;
295  case MISSING_TAG:
296  missing_tag_error(el.file, el.line, el.tag, el.n, el.value, create_exceptions_);
297  break;
298  case EXTRA_KEY:
300  break;
301  case WRONG_VALUE:
303  break;
304  case MISSING_KEY:
306  }
307 }
308 } // namespace schema_validation{
std::string lineno_string(const std::string &lineno)
bool create_exceptions_
Controls the way to print errors.
static config_cache & instance()
Get reference to the singleton object.
static void wrong_value_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &value, bool flag_exception)
cnt_stack counter_
Contains number of children.
static void wrong_tag_error(const std::string &file, int line, const std::string &name, const std::string &parent, bool flag_exception)
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
virtual void close_tag()
As far as parser is built on stack, some realizations can store stack too.
child_itors child_range(config_key_type key)
Definition: config.cpp:366
virtual void validate(const config &cfg, const std::string &name, int start_line, const std::string &file)
Validates config.
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:744
static void missing_tag_error(const std::string &file, int line, const std::string &name, int n, const std::string &parent, bool flag_exception)
class_key is used to save the information about one key.
Definition: tag.hpp:37
bool config_read_
Shows, if validator is initialized with schema file.
class_tag root_
Root of schema information.
One of the realizations of serialization/validator.hpp abstract validator.
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
void expand_all(class_tag &root)
Calls the expansion on each child.
Definition: tag.cpp:172
static void extra_key_error(const std::string &file, int line, const std::string &tag, const std::string &key, bool flag_exception)
std::stack< message_map > cache_
Caches error messages.
static void missing_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)
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:612
schema_validator(const std::string &filename)
Initializes validator from file.
bool strict_validation_enabled
Definition: validator.cpp:19
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
static void print_output(const std::string &message, bool flag_exception=false)
const char * what() const noexcept
Definition: exceptions.hpp:37
virtual void validate_key(const config &cfg, const std::string &name, const std::string &value, int start_line, const std::string &file)
Checks if key is allowed and if its value is valid What exactly is validated depends on validator rea...
virtual void open_tag(const std::string &name, int start_line=0, const std::string &file="", bool addittion=false)
Is called when parser opens tag.
#define LOG_VL
Helper class, don&#39;t construct this directly.
bool read_config_file(const std::string &filename)
Reads config from input.
double g
Definition: astarsearch.cpp:64
Declarations for File-IO.
std::stack< const class_tag * > stack_
const std::string & get_type() const
Definition: tag.hpp:63
Standard logging facilities (interface).
std::map< std::string, struct preproc_define > preproc_map
#define e
#define ERR_VL
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
static lg::log_domain log_validation("validation")
static map_location::DIRECTION n
Stores information about tag.
Definition: tag.hpp:140
std::map< std::string, boost::regex > types_
Type validators.
static void extra_tag_error(const std::string &file, int line, const std::string &name, int n, const std::string &parent, bool flag_exception)