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