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