The Battle for Wesnoth  1.15.0+dev
tag.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 
15 /**
16  * @file
17  * Implementation of tag.hpp.
18  */
19 
22 #include "formatter.hpp"
23 
24 namespace schema_validation
25 {
26 
27 wml_tag any_tag("", 0, -1, "", true);
28 
30  : name_(cfg["name"].str())
31  , min_(cfg["min"].to_int())
32  , max_(cfg["max"].str() == "infinite" ? -1 : cfg["max"].to_int(1))
33  , super_("")
34  , tags_()
35  , keys_()
36  , links_()
37  , conditions_()
38  , super_refs_()
39  , fuzzy_(name_.find_first_of("*?") != std::string::npos)
40  , any_tag_(cfg["any_tag"].to_bool())
41 {
42  if(max_ < 0) {
43  max_ = INT_MAX;
44  }
45 
46  if(cfg.has_attribute("super")) {
47  super_ = cfg["super"].str();
48  }
49 
50  for(const config& child : cfg.child_range("tag")) {
51  wml_tag child_tag(child);
52  add_tag(child_tag);
53  }
54 
55  for(const config& child : cfg.child_range("key")) {
56  wml_key child_key(child);
57  add_key(child_key);
58  }
59 
60  for(const config& link : cfg.child_range("link")) {
61  std::string link_name = link["name"].str();
62  add_link(link_name);
63  }
64 
65  for(const config& sw : cfg.child_range("switch")) {
66  add_switch(sw);
67  }
68 
69  for(const config& filter : cfg.child_range("if")) {
70  add_filter(filter);
71  }
72 }
73 
74 void wml_tag::print(std::ostream& os)
75 {
76  printl(os, 4, 4);
77 }
78 
79 void wml_tag::set_min(const std::string& s)
80 {
81  std::istringstream i(s);
82  if(!(i >> min_)) {
83  min_ = 0;
84  }
85 }
86 
87 void wml_tag::set_max(const std::string& s)
88 {
89  std::istringstream i(s);
90  if(!(i >> max_)) {
91  max_ = 0;
92  }
93 }
94 
95 void wml_tag::add_link(const std::string& link)
96 {
97  std::string::size_type pos_last = link.rfind('/');
98  // if (pos_last == std::string::npos) return;
99  std::string name_link = link.substr(pos_last + 1, link.length());
100  links_.emplace(name_link, link);
101 }
102 
103 const wml_key* wml_tag::find_key(const std::string& name, const config& match, bool ignore_super) const
104 {
105  // Check the conditions first, so that conditional definitions
106  // override base definitions in the event of duplicates.
107  for(auto& cond : conditions_) {
108  if(cond.matches(match)) {
109  if(auto key = cond.find_key(name, match, true)) {
110  return key;
111  }
112  }
113  }
114 
115  const auto it_keys = keys_.find(name);
116  if(it_keys != keys_.end()) {
117  return &(it_keys->second);
118  }
119 
120  key_map::const_iterator it_fuzzy = std::find_if(keys_.begin(), keys_.end(), [&name](const key_map::value_type& key){
121  if(!key.second.is_fuzzy()) {
122  return false;
123  }
124  return utils::wildcard_string_match(name, key.second.get_name());
125  });
126  if(it_fuzzy != keys_.end()) {
127  return &(it_fuzzy->second);
128  }
129 
130  if(!ignore_super) {
131  for(auto& cond : conditions_) {
132  if(cond.matches(match)) {
133  // This results in a little redundancy (checking things twice) but at least it still works.
134  if(auto key = cond.find_key(name, match, false)) {
135  return key;
136  }
137  }
138  }
139  for(auto& super_tag : super_refs_) {
140  if(const wml_key* found_key = super_tag->find_key(name, match)) {
141  return found_key;
142  }
143  }
144  }
145 
146  return nullptr;
147 }
148 
149 const std::string* wml_tag::find_link(const std::string& name) const
150 {
151  const auto it_links = links_.find(name);
152  if(it_links != links_.end()) {
153  return &(it_links->second);
154  }
155 
156  return nullptr;
157 }
158 
159 const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& root, const config& match, bool ignore_super) const
160 {
161  if(fullpath.empty()) {
162  return nullptr;
163  }
164 
165  std::string::size_type pos = fullpath.find('/');
166  std::string name;
167  std::string next_path;
168 
169  if(pos != std::string::npos) {
170  name = fullpath.substr(0, pos);
171  next_path = fullpath.substr(pos + 1, fullpath.length());
172  } else {
173  name = fullpath;
174  }
175 
176  // Check the conditions first, so that conditional definitions
177  // override base definitions in the event of duplicates.
178  for(auto& cond : conditions_) {
179  if(cond.matches(match)) {
180  if(auto tag = cond.find_tag(fullpath, root, match, true)) {
181  return tag;
182  }
183  }
184  }
185 
186  const auto it_tags = tags_.find(name);
187  if(it_tags != tags_.end()) {
188  if(next_path.empty()) {
189  return &(it_tags->second);
190  } else {
191  return it_tags->second.find_tag(next_path, root, match);
192  }
193  }
194 
195  const auto it_links = links_.find(name);
196  if(it_links != links_.end()) {
197  return root.find_tag(it_links->second + "/" + next_path, root, match);
198  }
199 
200  const auto it_fuzzy = std::find_if(tags_.begin(), tags_.end(), [&name](const tag_map::value_type& tag){
201  if(!tag.second.fuzzy_) {
202  return false;
203  }
204  return utils::wildcard_string_match(name, tag.second.name_);
205  });
206  if(it_fuzzy != tags_.end()) {
207  if(next_path.empty()) {
208  return &(it_fuzzy->second);
209  } else {
210  return it_tags->second.find_tag(next_path, root, match);
211  }
212  }
213 
214  if(!ignore_super) {
215  for(auto& cond : conditions_) {
216  if(cond.matches(match)) {
217  // This results in a little redundancy (checking things twice) but at least it still works.
218  if(auto tag = cond.find_tag(fullpath, root, match, false)) {
219  return tag;
220  }
221  }
222  }
223  for(auto& super_tag : super_refs_) {
224  if(const wml_tag* found_tag = super_tag->find_tag(fullpath, root, match)) {
225  return found_tag;
226  }
227  }
228  }
229 
230  if(any_tag_) {
231  return &any_tag;
232  }
233 
234  return nullptr;
235  }
236 
238 {
239  for(auto& tag : tags_) {
240  tag.second.expand(root);
241  tag.second.expand_all(root);
242  }
243  for(auto& cond : conditions_) {
244  cond.expand(root);
245  cond.expand_all(root);
246  }
247 }
248 
249 void wml_tag::remove_keys_by_type(const std::string& type)
250 {
251  auto i = keys_.begin();
252  while(i != keys_.end()) {
253  if(i->second.get_type() == type) {
254  keys_.erase(i++);
255  } else {
256  ++i;
257  }
258  }
259 
260  for(auto& tag : tags_) {
261  tag.second.remove_keys_by_type(type);
262  }
263 }
264 
265 /*WIKI
266  * @begin{parent}{name="wml_schema/"}
267  * @begin{tag}{name="tag"}{min=0}{max=1}
268  * @begin{table}{config}
269  * name & string & & The name of tag. $
270  * min & int & & The min number of occurrences. $
271  * max & int & & The max number of occurrences. $
272  * super & string & "" & The super-tag of this tag $
273  * @end{table}
274  * @begin{tag}{name="link"}{min=0}{max=-1}
275  * @begin{table}{config}
276  * name & string & & The name of link. $
277  * @end{table}
278  * @end{tag}{name="link"}
279  * @begin{tag}{name="tag"}{min=0}{max=-1}{super="wml_schema/tag"}
280  * @end{tag}{name="tag"}
281  * @end{tag}{name="tag"}
282  * @begin{tag}{name="type"}{min=0}{max=-1}
283  * @begin{table}{config}
284  * name & string & & The name of type. $
285  * value & string & & The value of the type, regex. $
286  * @end{table}
287  * @end{tag}{name="type"}
288  * @end{parent}{name="wml_schema/"}
289  */
290 void wml_tag::printl(std::ostream& os, int level, int step)
291 {
292  std::string s;
293  for(int j = 0; j < level; j++) {
294  s.append(" ");
295  }
296 
297  os << s << "[tag]\n"
298  << s << " name=\"" << name_ << "\"\n"
299  << s << " min=\"" << min_ << "\"\n"
300  << s << " max=\"" << max_ << "\"\n";
301 
302  if(!super_.empty()) {
303  os << s << " super=\"" << super_ << "\"\n";
304  }
305 
306  for(auto& tag : tags_) {
307  tag.second.printl(os, level + step, step);
308  }
309 
310  for(auto& link : links_) {
311  os << s << ""
312  << "[link]\n"
313  << s << ""
314  << " name=\"" << link.second << "\"\n"
315  << s << ""
316  << "[/link]\n";
317  }
318 
319  for(auto& key : keys_) {
320  key.second.print(os, level + step);
321  }
322 
323  // TODO: Other attributes
324 
325  os << s << "[/tag]\n";
326 }
327 
328 void wml_tag::add_tag(const std::string& path, const wml_tag& tag, wml_tag& root)
329 {
330  if(path.empty() || path == "/") {
331  auto it = tags_.find(tag.name_);
332 
333  if(it == tags_.end()) {
334  tags_.emplace(tag.name_, tag);
335  } else {
336  it->second.set_min(tag.min_);
337  it->second.set_max(tag.max_);
338  it->second.add_tags(tag.tags_);
339  it->second.add_keys(tag.keys_);
340  it->second.add_links(tag.links_);
341  // TODO: Other attributes
342  }
343 
344  links_.erase(tag.get_name());
345  return;
346  }
347 
348  std::string::size_type pos = path.find('/');
349  std::string name = path.substr(0, pos);
350  std::string next_path = path.substr(pos + 1, path.length());
351 
352  auto it_links = links_.find(name);
353  if(it_links != links_.end()) {
354  root.add_tag(it_links->second + "/" + next_path, tag, root);
355  }
356 
357  auto it_tags = tags_.find(name);
358  if(it_tags == tags_.end()) {
359  wml_tag subtag;
360  subtag.set_name(name);
361  subtag.add_tag(next_path, tag, root);
362  tags_.emplace(name, subtag);
363  return;
364  }
365 
366  it_tags->second.add_tag(next_path, tag, root);
367 }
368 
370 {
371  conditions_.insert(conditions_.end(), list.begin(), list.end());
372 }
373 
375 {
376  for(auto& super : utils::split(super_)) {
377  wml_tag* super_tag = root.find_tag(super, root, config());
378  if(super_tag) {
379  if(super_tag != this) {
380  super_refs_.push_back(super_tag);
381  } else {
382  // TODO: Detect super cycles too!
383  //std::cerr << "the same" << super_tag->name_ << "\n";
384  }
385  }
386  // TODO: Warn if the super doesn't exist
387  }
388 }
389 
390 void wml_tag::add_switch(const config& switch_cfg)
391 {
392  config default_cfg;
393  const std::string key = switch_cfg["key"];
394  bool allow_missing = false;
395  for(const auto& case_cfg : switch_cfg.child_range("case")) {
396  if(case_cfg.has_attribute("value")) {
397  const std::vector<std::string> values = utils::split(case_cfg["value"], ',', utils::STRIP_SPACES);
398  config filter;
399  for(const auto& value : values) {
400  // An [or] filter only works if there's something in the main filter.
401  // So, the first case cannot be wrapped in [or].
402  if(filter.empty()) {
403  filter[key] = value;
404  } else {
405  filter.add_child("or")[key] = value;
406  }
407  default_cfg.add_child("not")[key] = value;
408  }
409  if(!allow_missing && case_cfg["trigger_if_missing"].to_bool()) {
410  config& missing_filter = filter.add_child("or").add_child("not");
411  missing_filter["glob_on_" + key] = "*";
412  allow_missing = true;
413  }
414  conditions_.emplace_back(case_cfg, filter);
415  } else {
416  // Match if the attribute is missing
417  conditions_.emplace_back(case_cfg, config());
418  }
419  const std::string name = formatter() << get_name() << '[' << key << '=' << case_cfg["value"] << ']';
420  conditions_.back().set_name(name);
421  }
422  if(switch_cfg.has_child("else")) {
423  if(allow_missing) {
424  // If a [case] matches the absence of the key, then [else] should not
425  // The previous [not] keys already failed if it had a value matched by another [case]
426  // So just add an [and] tag that matches any other value
427  default_cfg.add_child("and")["glob_on_" + key] = "*";
428  }
429  conditions_.emplace_back(switch_cfg.child("else"), default_cfg);
430  const std::string name = formatter() << get_name() << "[else]";
431  conditions_.back().set_name(name);
432  }
433 }
434 
435 void wml_tag::add_filter(const config& cond_cfg)
436 {
437  config filter = cond_cfg, else_filter;
438  filter.clear_children("then", "else", "elseif");
439  // Note in case someone gets trigger-happy:
440  // DO NOT MOVE THIS! It needs to be copied!
441  else_filter.add_child("not", filter);
442  if(cond_cfg.has_child("then")) {
443  conditions_.emplace_back(cond_cfg.child("then"), filter);
444  const std::string name = formatter() << get_name() << "[then]";
445  conditions_.back().set_name(name);
446  }
447  int i = 1;
448  for(auto elseif_cfg : cond_cfg.child_range("elseif")) {
449  config elseif_filter = elseif_cfg, old_else_filter = else_filter;
450  elseif_filter.clear_children("then");
451  else_filter.add_child("not", elseif_filter);
452  // Ensure it won't match for any of the preceding cases, either
453  elseif_filter.append_children(old_else_filter);
454  conditions_.emplace_back(elseif_cfg.child_or_empty("then"), elseif_filter);
455  const std::string name = formatter() << get_name() << "[elseif " << i++ << "]";
456  conditions_.back().set_name(name);
457  }
458  if(cond_cfg.has_child("else")) {
459  conditions_.emplace_back(cond_cfg.child("else"), else_filter);
460  const std::string name = formatter() << get_name() << "[else]";
461  conditions_.back().set_name(name);
462  }
463 }
464 
465 bool wml_condition::matches(const config& cfg) const
466 {
467  if(cfg.empty()) {
468  // Conditions are not allowed to match an empty config.
469  // If they were, the conditions might be considered when expanding super-tags.
470  // That would result in a condition tag being used for the expansion, rather than
471  // the base tag, which would be bad.
472  return false;
473  }
474  return cfg.matches(filter_);
475 }
476 
477 template<>
479 {
480  current = base_tag.tags_.begin();
481  condition_queue.push(&base_tag);
482 }
483 
484 template<>
486  while(current == condition_queue.front()->tags_.end()) {
487  condition_queue.pop();
488  if(condition_queue.empty()) {
489  return;
490  }
491  const wml_tag& new_base = *condition_queue.front();
492  current= new_base.tags_.begin();
493  push_new_tag_conditions(new_base);
494  }
495 }
496 
497 template<>
498 void wml_tag::key_iterator::init(const wml_tag& base_tag)
499 {
500  current = base_tag.keys_.begin();
501  condition_queue.push(&base_tag);
502 }
503 
504 template<>
506  while(current == condition_queue.front()->keys_.end()) {
507  condition_queue.pop();
508  if(condition_queue.empty()) {
509  return;
510  }
511  const wml_tag& new_base = *condition_queue.front();
512  current = new_base.keys_.begin();
513  push_new_tag_conditions(new_base);
514  }
515 }
516 
517 void wml_tag::push_new_tag_conditions(std::queue<const wml_tag*>& q, const config& match, const wml_tag& tag) {
518  for(const auto& condition : tag.conditions_) {
519  if(condition.matches(match)) {
520  q.push(&condition);
521  }
522  }
523 }
524 
525 } // namespace schema_validation
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:420
bool matches(const config &filter) const
Definition: config.cpp:1229
void clear_children(T... keys)
Definition: config.hpp:509
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
child_itors child_range(config_key_type key)
Definition: config.cpp:362
void add_switch(const config &switch_cfg)
Definition: tag.cpp:390
Stores information about tag.
Definition: tag.hpp:46
bool wildcard_string_match(const std::string &str, const std::string &match)
Match using &#39;*&#39; as any number of characters (including none), &#39;+&#39; as one or more characters, and &#39;?&#39; as any one character.
void add_filter(const config &cond_cfg)
Definition: tag.cpp:435
STL namespace.
void remove_keys_by_type(const std::string &type)
Removes all keys with this type.
Definition: tag.cpp:249
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:241
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
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:103
void set_min(int o)
Definition: tag.hpp:195
This file contains object "tag", which is used to store information about tags while annotation parsi...
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
std::ostringstream wrapper.
Definition: formatter.hpp:38
void set_max(int o)
Definition: tag.hpp:200
void add_link(const std::string &link)
Definition: tag.cpp:95
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:159
REMOVE_EMPTY: remove empty elements.
bool matches(const config &cfg) const
Definition: tag.cpp:465
super_list super_refs_
super-tag references
Definition: tag.hpp:333
std::string path
Definition: game_config.cpp:39
void add_key(const wml_key &new_key)
Definition: tag.hpp:221
tag_map tags_
children tags
Definition: tag.hpp:321
std::vector< wml_condition > condition_list
Definition: tag.hpp:52
std::string name_
name of tag.
Definition: tag.hpp:303
static void push_new_tag_conditions(std::queue< const wml_tag *> &q, const config &match, const wml_tag &tag)
Definition: tag.cpp:517
void add_tag(const wml_tag &new_tag)
Definition: tag.hpp:226
wml_tag any_tag("", 0, -1, "", true)
condition_list conditions_
conditional partial matches
Definition: tag.hpp:330
std::size_t i
Definition: function.cpp:933
void init(const wml_tag &base_tag)
Definition: tag.cpp:478
void printl(std::ostream &os, int level, int step=4)
the same as wml_tag::print(std::ostream&) but indents different levels with step space.
Definition: tag.cpp:290
int min_
number of minimum occasions.
Definition: tag.hpp:306
void print(std::ostream &os)
Prints information about tag to outputstream, recursively is used to print tag info the format is nex...
Definition: tag.cpp:74
wml_key is used to save the information about one key.
Definition: key.hpp:35
link_map links_
links to possible children.
Definition: tag.hpp:327
static map_location::DIRECTION s
int max_
number of maximum occasions.
Definition: tag.hpp:309
bool any_tag_
whether this tag allows arbitrary subtags.
Definition: tag.hpp:339
static map_location::DIRECTION sw
config & add_child(config_key_type key)
Definition: config.cpp:476
void set_name(const std::string &name)
Definition: tag.hpp:190
void expand(wml_tag &root)
Expands all "super", storing direct references for easier access.
Definition: tag.cpp:374
void expand_all(wml_tag &root)
Calls the expansion on each child.
Definition: tag.cpp:237
const std::string & get_name() const
Definition: tag.hpp:157
void add_conditions(const condition_list &list)
Definition: tag.cpp:369
static int cond(LexState *ls)
Definition: lparser.cpp:1177
std::string super_
name of tag to extend "super-tag" Extension is smth like inheritance and is used in case when you nee...
Definition: tag.hpp:318
static const char * match(MatchState *ms, const char *s, const char *p)
Definition: lstrlib.cpp:425
key_map keys_
keys
Definition: tag.hpp:324
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
const std::string * find_link(const std::string &name) const
Returns pointer to child link.
Definition: tag.cpp:149
bool empty() const
Definition: config.cpp:884