The Battle for Wesnoth  1.19.0-dev
tag.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2024
3  by Sytyi Nick <nsytyi@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
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  auto visited = std::vector<const wml_tag*>();
112  return find_key(name, match, ignore_super, visited);
113 }
114 
115 const wml_key* wml_tag::find_key(const std::string& name, const config& match, bool ignore_super, std::vector<const wml_tag*>& visited) const
116 {
117  // Returns nullptr if a super cycle is detected.
118  if(std::find(visited.begin(), visited.end(), this) != visited.end()) {
119  return nullptr;
120  }
121 
122  visited.push_back(this);
123 
124  // Check the conditions first, so that conditional definitions
125  // override base definitions in the event of duplicates.
126  for(auto& cond : conditions_) {
127  if(cond.matches(match)) {
128  // Not considered for super cycle detection as super tags are ignored.
129  if(auto key = cond.find_key(name, match, true)) {
130  return key;
131  }
132  }
133  }
134 
135  const auto it_keys = keys_.find(name);
136  if(it_keys != keys_.end()) {
137  return &(it_keys->second);
138  }
139 
140  key_map::const_iterator it_fuzzy = std::find_if(keys_.begin(), keys_.end(), [&name](const key_map::value_type& key){
141  if(!key.second.is_fuzzy()) {
142  return false;
143  }
144  return utils::wildcard_string_match(name, key.second.get_name());
145  });
146  if(it_fuzzy != keys_.end()) {
147  return &(it_fuzzy->second);
148  }
149 
150  if(!ignore_super) {
151  for(auto& cond : conditions_) {
152  if(cond.matches(match)) {
153  // This results in a little redundancy (checking things twice) but at least it still works.
154  if(auto key = cond.find_key(name, match, false, visited)) {
155  return key;
156  }
157  }
158  }
159  for(auto& [_, super_tag] : super_refs_) {
160  if(const wml_key* found_key = super_tag->find_key(name, match, false, visited)) {
161  return found_key;
162  }
163  }
164  }
165 
166  return nullptr;
167 }
168 
169 const std::string* wml_tag::find_link(const std::string& name) const
170 {
171  const auto it_links = links_.find(name);
172  if(it_links != links_.end()) {
173  return &(it_links->second);
174  }
175 
176  return nullptr;
177 }
178 
179 const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& root, const config& match, bool ignore_super) const
180 {
181  auto visited = std::vector<const wml_tag*>();
182  return find_tag(fullpath, root, match, ignore_super, visited);
183 }
184 
185 const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& root, const config& match, bool ignore_super, std::vector<const wml_tag*>& visited) const
186 {
187  // Returns nullptr if a super cycle is detected.
188  if(std::find(visited.begin(), visited.end(), this) != visited.end()) {
189  return nullptr;
190  }
191 
192  visited.push_back(this);
193 
194  if(fullpath.empty()) {
195  return nullptr;
196  }
197 
198  std::string::size_type pos = fullpath.find('/');
199  std::string name;
200  std::string next_path;
201 
202  if(pos != std::string::npos) {
203  name = fullpath.substr(0, pos);
204  next_path = fullpath.substr(pos + 1, fullpath.length());
205  } else {
206  name = fullpath;
207  }
208 
209  // Check the conditions first, so that conditional definitions
210  // override base definitions in the event of duplicates.
211  for(auto& cond : conditions_) {
212  if(cond.matches(match)) {
213  // Not considered for super cycle detection as super tags are ignored.
214  if(auto tag = cond.find_tag(fullpath, root, match, true)) {
215  return tag;
216  }
217  }
218  }
219 
220  const auto it_tags = tags_.find(name);
221  if(it_tags != tags_.end()) {
222  if(next_path.empty()) {
223  return &(it_tags->second);
224  } else {
225  return it_tags->second.find_tag(next_path, root, match, false, visited);
226  }
227  }
228 
229  const auto it_links = links_.find(name);
230  if(it_links != links_.end()) {
231  // Reset cycle detection on links as we restart from the root.
232  return root.find_tag(it_links->second + "/" + next_path, root, match, false);
233  }
234 
235  const auto it_fuzzy = std::find_if(tags_.begin(), tags_.end(), [&name](const tag_map::value_type& tag) {
236  if(!tag.second.fuzzy_) {
237  return false;
238  }
239  return utils::wildcard_string_match(name, tag.second.name_);
240  });
241  if(it_fuzzy != tags_.end()) {
242  if(next_path.empty()) {
243  return &(it_fuzzy->second);
244  } else {
245  return it_tags->second.find_tag(next_path, root, match, false, visited);
246  }
247  }
248 
249  if(!ignore_super) {
250  for(auto& cond : conditions_) {
251  if(cond.matches(match)) {
252  // This results in a little redundancy (checking things twice) but at least it still works.
253  if(auto tag = cond.find_tag(fullpath, root, match, false, visited)) {
254  return tag;
255  }
256  }
257  }
258  for(auto& [_, super_tag] : super_refs_) {
259  if(const wml_tag* found_tag = super_tag->find_tag(fullpath, root, match, false, visited)) {
260  return found_tag;
261  }
262  }
263  }
264 
265  if(any_tag_) {
266  return &any_tag;
267  }
268 
269  return nullptr;
270  }
271 
272 void wml_tag::expand_all(wml_tag& root)
273 {
274  for(auto& tag : tags_) {
275  tag.second.expand(root);
276  tag.second.expand_all(root);
277  }
278  for(auto& cond : conditions_) {
279  cond.expand(root);
280  cond.expand_all(root);
281  }
282 }
283 
284 void wml_tag::remove_keys_by_type(const std::string& type)
285 {
286  auto i = keys_.begin();
287  while(i != keys_.end()) {
288  if(i->second.get_type() == type) {
289  keys_.erase(i++);
290  } else {
291  ++i;
292  }
293  }
294 
295  for(auto& tag : tags_) {
296  tag.second.remove_keys_by_type(type);
297  }
298 }
299 
300 void wml_tag::printl(std::ostream& os, int level, int step)
301 {
302  std::string s;
303  for(int j = 0; j < level; j++) {
304  s.append(" ");
305  }
306 
307  os << s << "[tag]\n"
308  << s << " name=\"" << name_ << "\"\n"
309  << s << " min=\"" << min_ << "\"\n"
310  << s << " max=\"" << max_ << "\"\n";
311 
312  if(!super_.empty()) {
313  os << s << " super=\"" << super_ << "\"\n";
314  }
315 
316  for(auto& tag : tags_) {
317  tag.second.printl(os, level + step, step);
318  }
319 
320  for(auto& link : links_) {
321  os << s << ""
322  << "[link]\n"
323  << s << ""
324  << " name=\"" << link.second << "\"\n"
325  << s << ""
326  << "[/link]\n";
327  }
328 
329  for(auto& key : keys_) {
330  key.second.print(os, level + step);
331  }
332 
333  // TODO: Other attributes
334 
335  os << s << "[/tag]\n";
336 }
337 
338 void wml_tag::add_tag(const std::string& path, const wml_tag& tag, wml_tag& root)
339 {
340  if(path.empty() || path == "/") {
341  auto it = tags_.find(tag.name_);
342 
343  if(it == tags_.end()) {
344  tags_.emplace(tag.name_, tag);
345  } else {
346  it->second.set_min(tag.min_);
347  it->second.set_max(tag.max_);
348  it->second.add_tags(tag.tags_);
349  it->second.add_keys(tag.keys_);
350  it->second.add_links(tag.links_);
351  // TODO: Other attributes
352  }
353 
354  links_.erase(tag.get_name());
355  return;
356  }
357 
358  std::string::size_type pos = path.find('/');
359  std::string name = path.substr(0, pos);
360  std::string next_path = path.substr(pos + 1, path.length());
361 
362  auto it_links = links_.find(name);
363  if(it_links != links_.end()) {
364  root.add_tag(it_links->second + "/" + next_path, tag, root);
365  }
366 
367  auto it_tags = tags_.find(name);
368  if(it_tags == tags_.end()) {
369  wml_tag subtag;
370  subtag.set_name(name);
371  subtag.add_tag(next_path, tag, root);
372  tags_.emplace(name, subtag);
373  return;
374  }
375 
376  it_tags->second.add_tag(next_path, tag, root);
377 }
378 
379 void wml_tag::add_conditions(const condition_list& list)
380 {
381  conditions_.insert(conditions_.end(), list.begin(), list.end());
382 }
383 
384 void wml_tag::expand(wml_tag& root)
385 {
386  for(auto& super : utils::split(super_)) {
387  wml_tag* super_tag = root.find_tag(super, root, config());
388  if(super_tag) {
389  if(super_tag != this) {
390  super_refs_.emplace(super, super_tag);
391  }
392  }
393  }
394 }
395 
396 void wml_tag::add_switch(const config& switch_cfg)
397 {
398  config default_cfg;
399  const std::string key = switch_cfg["key"];
400  bool allow_missing = false;
401  for(const auto& case_cfg : switch_cfg.child_range("case")) {
402  if(case_cfg.has_attribute("value")) {
403  const std::vector<std::string> values = utils::split(case_cfg["value"].str(), ',', utils::STRIP_SPACES);
404  config filter;
405  for(const auto& value : values) {
406  // An [or] filter only works if there's something in the main filter.
407  // So, the first case cannot be wrapped in [or].
408  if(filter.empty()) {
409  filter[key] = value;
410  } else {
411  filter.add_child("or")[key] = value;
412  }
413  default_cfg.add_child("not")[key] = value;
414  }
415  if(!allow_missing && case_cfg["trigger_if_missing"].to_bool()) {
416  config& missing_filter = filter.add_child("or").add_child("not");
417  missing_filter["glob_on_" + key] = "*";
418  allow_missing = true;
419  }
420  conditions_.emplace_back(case_cfg, filter);
421  } else {
422  // Match if the attribute is missing
423  conditions_.emplace_back(case_cfg, config());
424  }
425  const std::string name = formatter() << get_name() << '[' << key << '=' << case_cfg["value"] << ']';
426  conditions_.back().set_name(name);
427  }
428  if(switch_cfg.has_child("else")) {
429  if(allow_missing) {
430  // If a [case] matches the absence of the key, then [else] should not
431  // The previous [not] keys already failed if it had a value matched by another [case]
432  // So just add an [and] tag that matches any other value
433  default_cfg.add_child("and")["glob_on_" + key] = "*";
434  }
435  conditions_.emplace_back(switch_cfg.mandatory_child("else"), default_cfg);
436  const std::string name = formatter() << get_name() << "[else]";
437  conditions_.back().set_name(name);
438  }
439 }
440 
441 void wml_tag::add_filter(const config& cond_cfg)
442 {
443  config filter = cond_cfg, else_filter;
444  filter.clear_children("then", "else", "elseif");
445  // Note in case someone gets trigger-happy:
446  // DO NOT MOVE THIS! It needs to be copied!
447  else_filter.add_child("not", filter);
448  if(cond_cfg.has_child("then")) {
449  conditions_.emplace_back(cond_cfg.mandatory_child("then"), filter);
450  const std::string name = formatter() << get_name() << "[then]";
451  conditions_.back().set_name(name);
452  }
453  int i = 1;
454  for(auto elseif_cfg : cond_cfg.child_range("elseif")) {
455  config elseif_filter = elseif_cfg, old_else_filter = else_filter;
456  elseif_filter.clear_children("then");
457  else_filter.add_child("not", elseif_filter);
458  // Ensure it won't match for any of the preceding cases, either
459  elseif_filter.append_children(old_else_filter);
460  conditions_.emplace_back(elseif_cfg.child_or_empty("then"), elseif_filter);
461  const std::string name = formatter() << get_name() << "[elseif " << i++ << "]";
462  conditions_.back().set_name(name);
463  }
464  if(cond_cfg.has_child("else")) {
465  conditions_.emplace_back(cond_cfg.mandatory_child("else"), else_filter);
466  const std::string name = formatter() << get_name() << "[else]";
467  conditions_.back().set_name(name);
468  }
469 }
470 
471 bool wml_condition::matches(const config& cfg) const
472 {
473  if(cfg.empty()) {
474  // Conditions are not allowed to match an empty config.
475  // If they were, the conditions might be considered when expanding super-tags.
476  // That would result in a condition tag being used for the expansion, rather than
477  // the base tag, which would be bad.
478  return false;
479  }
480  return cfg.matches(filter_);
481 }
482 
483 template<>
485 {
486  current = base_tag.tags_.begin();
487  condition_queue.push(&base_tag);
488 }
489 
490 template<>
491 void wml_tag::tag_iterator::ensure_valid_or_end() {
492  while(current == condition_queue.front()->tags_.end()) {
493  condition_queue.pop();
494  if(condition_queue.empty()) {
495  return;
496  }
497  const wml_tag& new_base = *condition_queue.front();
498  current= new_base.tags_.begin();
499  push_new_tag_conditions(new_base);
500  }
501 }
502 
503 template<>
504 void wml_tag::key_iterator::init(const wml_tag& base_tag)
505 {
506  current = base_tag.keys_.begin();
507  condition_queue.push(&base_tag);
508 }
509 
510 template<>
511 void wml_tag::key_iterator::ensure_valid_or_end() {
512  while(current == condition_queue.front()->keys_.end()) {
513  condition_queue.pop();
514  if(condition_queue.empty()) {
515  return;
516  }
517  const wml_tag& new_base = *condition_queue.front();
518  current = new_base.keys_.begin();
519  push_new_tag_conditions(new_base);
520  }
521 }
522 
523 template<>
524 void wml_tag::super_iterator::init(const wml_tag& base_tag)
525 {
526  current = base_tag.super_refs_.begin();
527  condition_queue.push(&base_tag);
528 }
529 
530 template<>
531 void wml_tag::super_iterator::ensure_valid_or_end()
532 {
533  while(current == condition_queue.front()->super_refs_.end()) {
534  condition_queue.pop();
535  if(condition_queue.empty()) {
536  return;
537  }
538  const wml_tag& new_base = *condition_queue.front();
539  current = new_base.super_refs_.begin();
540  push_new_tag_conditions(new_base);
541  }
542 }
543 
544 void wml_tag::push_new_tag_conditions(std::queue<const wml_tag*>& q, const config& match, const wml_tag& tag)
545 {
546  for(const auto& condition : tag.conditions_) {
547  if(condition.matches(match)) {
548  q.push(&condition);
549  }
550  }
551 }
552 
553 } // namespace schema_validation
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
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:367
bool matches(const config &filter) const
Definition: config.cpp:1198
void clear_children(T... keys)
Definition: config.hpp:642
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:317
bool has_attribute(config_key_type key) const
Definition: config.cpp:155
child_itors child_range(config_key_type key)
Definition: config.cpp:273
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:165
bool empty() const
Definition: config.cpp:852
config & add_child(config_key_type key)
Definition: config.cpp:441
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:384
const std::string & get_name() const
Definition: tag.hpp:162
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:356
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:179
void set_max(int o)
Definition: tag.hpp:215
void set_name(const std::string &name)
Definition: tag.hpp:205
void add_filter(const config &cond_cfg)
Definition: tag.cpp:441
void add_tag(const wml_tag &new_tag)
Definition: tag.hpp:254
void set_min(int o)
Definition: tag.hpp:210
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:365
std::string name_
name of tag.
Definition: tag.hpp:335
int min_
minimum number of occurrences.
Definition: tag.hpp:338
void add_switch(const config &switch_cfg)
Definition: tag.cpp:396
condition_list conditions_
conditional partial matches
Definition: tag.hpp:368
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:359
void add_key(const wml_key &new_key)
Definition: tag.hpp:249
int max_
maximum number of occurrences.
Definition: tag.hpp:341
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:300
int max_children_
maximum number of children.
Definition: tag.hpp:347
std::size_t i
Definition: function.cpp:968
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string path
Definition: filesystem.cpp:84
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