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