The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
config.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
3 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4 Copyright (C) 2003 by David White <dave@whitevine.net>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
13
14 See the COPYING file for more details.
15*/
16
17/**
18 * @file
19 * Routines related to configuration-files / WML.
20 */
21
22#include "config.hpp"
23
24#include "formatter.hpp"
25#include "log.hpp"
26#include "deprecation.hpp"
27#include "game_version.hpp"
30#include "utils/general.hpp"
31
32#include <algorithm>
33#include <cstring>
34#include <iterator>
35
36static lg::log_domain log_config("config");
37#define ERR_CF LOG_STREAM(err, log_config)
38#define DBG_CF LOG_STREAM(debug, log_config)
39
40static lg::log_domain log_wml("wml");
41#define ERR_WML LOG_STREAM(err, log_wml)
42
43namespace
44{
45// std::map::operator[] does not support heterogeneous lookup so we need this to work around.
46template<typename Map, typename Key>
47typename Map::iterator map_get(Map& map, Key&& key)
48{
49 auto res = map.lower_bound(key);
50
51 if(res == map.end() || key != res->first) {
52 res = map.emplace_hint(res, std::piecewise_construct, std::forward_as_tuple(key), std::tuple<>());
53 }
54
55 return res;
56}
57
58// std::map::erase does not support heterogeneous lookup so we need this to work around.
59template<typename Map, typename Key>
60int map_erase_key(Map& map, Key&& key)
61{
62 auto i = map.find(key);
63 if(i != map.end()) {
64 map.erase(i);
65 return 1;
66 }
67 return 0;
68}
69
70}
71
72/* ** config implementation ** */
73
74const char* config::diff_track_attribute = "__diff_track";
75
77 : values_()
78 , children_()
79 , ordered_children_()
80{
81}
82
84 : values_(cfg.values_)
85 , children_()
86 , ordered_children_()
87{
89}
90
91config::config(std::string_view child)
92 : values_()
93 , children_()
94 , ordered_children_()
95{
96 add_child(child);
97}
98
100{
101 clear();
102}
103
105{
106 if(this == &cfg) {
107 return *this;
108 }
109
110 config tmp(cfg);
111 swap(tmp);
112 return *this;
113}
114
116 : values_(std::move(cfg.values_))
117 , children_(std::move(cfg.children_))
118 , ordered_children_(std::move(cfg.ordered_children_))
119{
120}
121
123{
124 clear();
125 swap(cfg);
126 return *this;
127}
128
129bool config::valid_tag(std::string_view name)
130{
131 if(name == "") {
132 // Empty strings not allowed
133 return false;
134 } else if(name == "_") {
135 // A lone underscore isn't a valid tag name
136 return false;
137 } else {
138 return std::all_of(name.begin(), name.end(), [](const char& c)
139 {
140 /* Only alphanumeric ASCII characters and underscores are allowed.
141
142 We're using a manual check mainly for performance. @gfgtdf measured
143 that a manual check can be up to 30 times faster than std::isalnum().
144
145 - Jyrki, 2019-01-19 */
146 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
147 (c >= '0' && c <= '9') || (c == '_');
148 });
149 }
150}
151
152bool config::valid_attribute(std::string_view name)
153{
154 return valid_tag(name);
155}
156
157bool config::has_attribute(std::string_view key) const
159 return values_.find(key) != values_.end();
160}
161
162void config::remove_attribute(std::string_view key)
163{
164 map_erase_key(values_, key);
165}
166
168{
169 for(const auto [key, cfg] : cfg.all_children_view()) {
170 add_child(key, cfg);
171 }
172}
173
175{
176 for(const auto& [key, value] : cfg.values_) {
177 values_[key] = value;
178 }
179}
180
181void config::append_children(const config& cfg, std::string_view key)
182{
183 for(const config& value : cfg.child_range(key)) {
184 add_child(key, value);
185 }
186}
187
189{
191 for(const auto& [key, value] : cfg.values_) {
192 values_[key] = value;
193 }
194}
195
197{
198 if(children_.empty()) {
199 //optimisation
200 children_ = std::move(cfg.children_);
203 }
204 else {
205 for(const auto [child_key, child_value] : cfg.all_children_view()) {
206 add_child(child_key, std::move(child_value));
207 }
209 }
210
211 if(values_.empty()) {
212 //optimisation.
213 values_ = std::move(cfg.values_);
214 }
215 else {
216 for(const auto& [key, value] : cfg.values_) {
217 //TODO: move the attributes as well?
218 values_[key] = value;
219 }
220 }
222}
223
224void config::append_children_by_move(config& cfg, std::string_view key)
225{
226 // DO note this leaves the tags empty in the source config. Not sure if
227 // that should be changed.
228 for(config& value : cfg.child_range(key)) {
229 add_child(key, std::move(value));
230 }
231
233}
234
235void config::merge_children(std::string_view key)
236{
237 if(child_count(key) < 2) {
238 return;
239 }
240
241 config merged_children;
242 for(config& cfg : child_range(key)) {
243 merged_children.append(std::move(cfg));
244 }
245
247 add_child(key, std::move(merged_children));
248}
249
250void config::merge_children_by_attribute(std::string_view key, std::string_view attribute)
251{
252 if(child_count(key) < 2) {
253 return;
254 }
255
256 typedef std::map<std::string, config> config_map;
257 config_map merged_children_map;
258 for(config& cfg : child_range(key)) {
259 merged_children_map[cfg[attribute]].append(std::move(cfg));
260 }
261
263 for(const config_map::value_type& i : merged_children_map) {
264 add_child(key, i.second); // TODO: can we use std::move?
265 }
266}
267
269{
270 child_map::iterator i = children_.find(key);
271 static child_list dummy;
272 child_list* p = &dummy;
273 if(i != children_.end()) {
274 p = &i->second;
275 }
276
277 return child_itors(child_iterator(p->begin()), child_iterator(p->end()));
278}
279
281{
282 child_map::const_iterator i = children_.find(key);
283 static child_list dummy;
284 const child_list* p = &dummy;
285 if(i != children_.end()) {
286 p = &i->second;
287 }
288
290}
291
292std::size_t config::child_count(std::string_view key) const
293{
294 child_map::const_iterator i = children_.find(key);
295 if(i != children_.end()) {
296 return i->second.size();
297 }
298
299 return 0;
300}
301
302std::size_t config::all_children_count() const
303{
304 return ordered_children_.size();
305}
306
307std::size_t config::attribute_count() const
308{
309 return std::count_if(values_.begin(), values_.end(), [](const attribute& v) { return !v.second.blank(); });
310}
311
312bool config::has_child(std::string_view key) const
313{
314 child_map::const_iterator i = children_.find(key);
315 return i != children_.end() && !i->second.empty();
316}
317
318namespace {
319template<class Tchildren>
320auto get_child_impl(Tchildren& children, std::string_view key, int n) -> optional_config_impl<std::remove_reference_t<decltype(**(*children.begin()).second.begin())>>
321{
322
323 auto i = children.find(key);
324 if(i == children.end()) {
325 DBG_CF << "The config object has no child named ‘" << key << "’.";
326 return utils::nullopt;
327 }
328
329 if(n < 0) {
330 n = static_cast<int>(i->second.size()) + n;
331 }
332
333 try {
334 return *i->second.at(n);
335 } catch(const std::out_of_range&) {
336 DBG_CF << "The config object has only ‘" << i->second.size() << "’ children named ‘" << key
337 << "’; request for the index ‘" << n << "’ cannot be honored.";
338 return utils::nullopt;
339 }
340}
341
342}
343
344config& config::mandatory_child(std::string_view key, const std::string& parent)
345{
346 if(auto res = get_child_impl(children_, key, 0)) {
347 return *res;
348 } else {
349 throw error("Mandatory WML child ‘[" + std::string(key) + "]’ missing in ‘" + parent + "’. Please report this bug.");
350 }
351}
352
353const config& config::mandatory_child(std::string_view key, const std::string& parent) const
354{
355 if(auto res = get_child_impl(children_, key, 0)) {
356 return *res;
357 } else {
358 throw error("Mandatory WML child ‘[" + std::string(key) + "]’ missing in ‘" + parent + "’. Please report this bug.");
359 }
360}
361
362config& config::mandatory_child(std::string_view key, int n)
363{
364 if(auto res = get_child_impl(children_, key, n)) {
365 return *res;
366 } else {
367 throw error("Child [" + std::string(key) + "] at index " + std::to_string(n) + " not found");
368 }
369}
370
371const config& config::mandatory_child(std::string_view key, int n) const
372{
373 if(auto res = get_child_impl(children_, key, n)) {
374 return *res;
375 } else {
376 throw error("Child [" + std::string(key) + "] at index " + std::to_string(n) + " not found");
377 }
378}
379
380optional_config config::optional_child(std::string_view key, int n)
381{
382 return get_child_impl(children_, key, n);
383}
384
385optional_const_config config::optional_child(std::string_view key, int n) const
386{
387 return get_child_impl(children_, key, n);
388}
389
390const config& config::child_or_empty(std::string_view key) const
391{
392 static const config empty_cfg;
393 child_map::const_iterator i = children_.find(key);
394 if(i != children_.end() && !i->second.empty()) {
395 return *i->second.front();
396 }
397
398 return empty_cfg;
399}
400
401config& config::child_or_add(std::string_view key)
402{
403 child_map::const_iterator i = children_.find(key);
404 if(i != children_.end() && !i->second.empty()) {
405 return *i->second.front();
406 }
407
408 return add_child(key);
409}
410
411optional_config_impl<const config> config::get_deprecated_child(std::string_view old_key, const std::string& in_tag, DEP_LEVEL level, const std::string& message) const
412{
413 if(auto res = optional_child(old_key)) {
414 const std::string what = formatter() << "[" << in_tag << "][" << old_key << "]";
415 deprecated_message(what, level, "", message);
416 return res;
417 }
418
419 return utils::nullopt;
420}
421
422config::const_child_itors config::get_deprecated_child_range(std::string_view old_key, const std::string& in_tag, DEP_LEVEL level, const std::string& message) const
423{
424 static child_list dummy;
425 const child_list* p = &dummy;
426
427 if(auto i = children_.find(old_key); i != children_.end() && !i->second.empty()) {
428 const std::string what = formatter() << "[" << in_tag << "][" << old_key << "]";
429 deprecated_message(what, level, "", message);
430 p = &i->second;
431 }
432
434}
435
436config& config::add_child(std::string_view key)
437{
438 auto iter = map_get(children_, key);
439 child_list& v = iter->second;
440 v.emplace_back(new config());
441 ordered_children_.emplace_back(iter, v.size() - 1);
442 return *v.back();
443}
444
445config& config::add_child(std::string_view key, const config& val)
446{
447 auto iter = map_get(children_, key);
448 child_list& v = iter->second;
449 v.emplace_back(new config(val));
450 ordered_children_.emplace_back(iter, v.size() - 1);
451
452 return *v.back();
453}
454
455config& config::add_child(std::string_view key, config&& val)
456{
457 auto iter = map_get(children_, key);
458 child_list& v = iter->second;
459 v.emplace_back(new config(std::move(val)));
460 ordered_children_.emplace_back(iter, v.size() - 1);
461
462 return *v.back();
463}
464
465config& config::add_child_at(std::string_view key, const config& val, std::size_t index)
466{
467 auto iter = map_get(children_, key);
468 child_list& v = iter->second;
469 if(index > v.size()) {
470 throw error("illegal index to add child at");
471 }
472
473 v.emplace(v.begin() + index, new config(val));
474
475 bool inserted = false;
476
477 const child_pos value(iter, index);
478
479 std::vector<child_pos>::iterator ord = ordered_children_.begin();
480 for(; ord != ordered_children_.end(); ++ord) {
481 if(ord->pos != value.pos)
482 continue;
483 if(!inserted && ord->index == index) {
484 ord = ordered_children_.insert(ord, value);
485 inserted = true;
486 } else if(ord->index >= index) {
487 ord->index++;
488 }
489 }
490
491 if(!inserted) {
492 ordered_children_.push_back(value);
493 }
494
495 return *v[index];
496}
497
498std::size_t config::find_total_first_of(std::string_view key, std::size_t start)
499{
500 assert(start <= ordered_children_.size());
501 const std::size_t npos = static_cast<std::size_t>(-1);
502
503 auto pos = std::find_if(ordered_begin() + start, ordered_end(), [&](const config::any_child& can){ return can.key == key; });
504
505 if(pos == ordered_end()) {
506 return npos;
507 }
508
509 return static_cast<std::size_t>(pos - ordered_begin());
510}
511
512config& config::add_child_at_total(std::string_view key, const config &val, std::size_t pos)
513{
514 assert(pos <= ordered_children_.size());
515 if(pos == ordered_children_.size()) {
516 //optimisation
517 return config::add_child(key, val);
518 }
519
520 auto end = ordered_children_.end();
521 auto pos_it = ordered_children_.begin() + pos;
522 auto next = std::find_if(pos_it, end,[&](const child_pos& p){ return p.pos->first == key; });
523
524 if(next == end) {
525 config& res = config::add_child(key, val);
526 //rotate the just inserted element to position pos.
527 std::rotate(ordered_children_.begin() + pos, ordered_children_.end() - 1, ordered_children_.end());
528 return res;
529 }
530
531 auto pl = next->pos;
532 child_list& l = pl->second;
533 const auto index = next->index;
534 config& res = **(l.emplace(l.begin() + index, new config(val)));
535
536 for(auto ord = next; ord != end; ++ord) {
537 //this changes next->index and all later refernces to that tag.
538 if(ord->pos == pl) {
539 ++ord->index;
540 }
541 }
542
543 //finally insert our new child in ordered_children_.
544 ordered_children_.insert(pos_it, { pl, index });
545 return res;
546}
547
548void config::clear_children_impl(std::string_view key)
549{
550 child_map::iterator i = children_.find(key);
551 if(i == children_.end())
552 return;
553
554 utils::erase_if(ordered_children_, [&i](const config::child_pos& pos) { return pos.pos == i; });
556}
557
558void config::splice_children(config& src, std::string_view key)
559{
560 child_map::iterator i_src = src.children_.find(key);
561 if(i_src == src.children_.end()) {
562 return;
563 }
564
565 utils::erase_if(src.ordered_children_, [&i_src](const config::child_pos& pos) { return pos.pos == i_src; });
566
567 auto i_dst = map_get(children_, key);
568 child_list& dst = i_dst->second;
569
570 const auto before = dst.size();
571 dst.insert(dst.end(),
572 std::move_iterator{i_src->second.begin()},
573 std::move_iterator{i_src->second.end()});
574
575 src.children_.erase(i_src);
576 // key might be a reference to i_src->first, so it is no longer usable.
577
578 for(std::size_t j = before; j < dst.size(); ++j) {
579 ordered_children_.emplace_back(i_dst, j);
580 }
581}
582
583void config::recursive_clear_value(std::string_view key)
584{
585 map_erase_key(values_, key);
586
587 for(std::pair<const std::string, child_list>& p : children_) {
588 for(auto& cfg : p.second) {
590 }
591 }
592}
593
594std::vector<config::child_pos>::iterator config::remove_child(const child_map::iterator& pos, std::size_t index)
595{
596 /* Find the position with the correct index and decrement all the
597 indices in the ordering that are above this index. */
598 std::size_t found = 0;
600 if(p.pos != pos) {
601 continue;
602 }
603
604 if(p.index == index) {
605 found = &p - &ordered_children_.front();
606 } else if(p.index > index) {
607 --p.index;
608 }
609 }
610
611 // Remove from the child map.
612 pos->second.erase(pos->second.begin() + index);
613
614 // Erase from the ordering and return the next position.
615 return ordered_children_.erase(ordered_children_.begin() + found);
616}
617
622
623void config::remove_child(std::string_view key, std::size_t index)
624{
625 child_map::iterator i = children_.find(key);
626 if(i == children_.end() || index >= i->second.size()) {
627 ERR_CF << "Error: attempting to delete non-existing child: " << key << "[" << index << "]";
628 return;
629 }
630
631 remove_child(i, index);
632}
633
634void config::remove_children(std::string_view key, const std::function<bool(const config&)>& p)
635{
636 child_map::iterator pos = children_.find(key);
637 if(pos == children_.end()) {
638 return;
639 }
640
641 const auto predicate = [p](const std::unique_ptr<config>& child)
642 {
643 return !p || p(*child);
644 };
645
646 auto child_it = std::find_if(pos->second.begin(), pos->second.end(), predicate);
647 while(child_it != pos->second.end()) {
648 const auto index = std::distance(pos->second.begin(), child_it);
649 remove_child(pos, index);
650 child_it = std::find_if(pos->second.begin() + index, pos->second.end(), predicate);
651 }
652}
653
654const config::attribute_value& config::operator[](std::string_view key) const
655{
656 const attribute_map::const_iterator i = values_.find(key);
657 if(i != values_.end()) {
658 return i->second;
659 }
660
661 static const attribute_value empty_attribute;
662 return empty_attribute;
663}
664
665const config::attribute_value* config::get(std::string_view key) const
666{
667 attribute_map::const_iterator i = values_.find(key);
668 return i != values_.end() ? &i->second : nullptr;
669}
670
671const config::attribute_value& config::get_or(const std::string_view key, const std::string_view default_key) const
672{
674 return !value.blank() ? value : operator[](default_key);
675}
676
678{
679 auto res = values_.lower_bound(key);
680
681 if(res == values_.end() || key != res->first) {
682 res = values_.emplace_hint(res, std::piecewise_construct, std::forward_as_tuple(key), std::tuple<>());
683 }
684
685 return res->second;
686}
687
688const config::attribute_value& config::get_old_attribute(std::string_view key, const std::string& old_key, const std::string& in_tag, const std::string& message) const
689{
690 if(has_attribute(old_key)) {
691 const std::string what = formatter() << "[" << in_tag << "]" << old_key << "=";
692 const std::string msg = formatter() << "Use " << key << "= instead. " << message;
694 }
695
696 attribute_map::const_iterator i = values_.find(key);
697 if(i != values_.end()) {
698 return i->second;
699 }
700
701 i = values_.find(old_key);
702 if(i != values_.end()) {
703 return i->second;
704 }
705
706 static const attribute_value empty_attribute;
707 return empty_attribute;
708}
709
710const config::attribute_value& config::get_deprecated_attribute(std::string_view old_key, const std::string& in_tag, DEP_LEVEL level, const std::string& message) const
711{
712 if(auto i = values_.find(old_key); i != values_.end()) {
713 const std::string what = formatter() << "[" << in_tag << "]" << old_key << "=";
714 deprecated_message(what, level, "", message);
715 return i->second;
716 }
717
718 static const attribute_value empty_attribute;
719 return empty_attribute;
720}
721
723{
724 assert(this != &cfg);
725 for(const auto& [key, value] : cfg.values_) {
726 if(key.substr(0, 7) == "add_to_") {
727 std::string add_to = key.substr(7);
728 values_[add_to] = values_[add_to].to_double() + value.to_double();
729 } else if(key.substr(0, 10) == "concat_to_") {
730 std::string concat_to = key.substr(10);
731 // TODO: Only use t_string if one or both are actually translatable?
732 // That probably requires using a visitor though.
733 values_[concat_to] = values_[concat_to].t_str() + value.t_str();
734 } else {
735 values_[key] = value;
736 }
737 }
738}
739
741{
743
744 // Ensure the first element is not blank, as a few places assume this
745 while(range.begin() != range.end() && range.begin()->second.blank()) {
746 range.pop_front();
747 }
748
749 return range;
750}
751
753{
755
756 // Ensure the first element is not blank, as a few places assume this
757 while(range.begin() != range.end() && range.begin()->second.blank()) {
758 range.pop_front();
759 }
760
761 return range;
762}
763
764optional_config config::find_child(std::string_view key, const std::string& name, const std::string& value)
765{
766 const child_map::iterator i = children_.find(key);
767 if(i == children_.end()) {
768 DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
769 return utils::nullopt;
770 }
771
772 const child_list::iterator j = utils::ranges::find(i->second, value,
773 [&](const std::unique_ptr<config>& pcfg) -> const auto& { return (*pcfg)[name]; });
774
775 if(j != i->second.end()) {
776 return **j;
777 }
778
779 DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
780
781 return utils::nullopt;
782}
783
784config& config::find_mandatory_child(std::string_view key, const std::string &name, const std::string &value)
785{
786 auto res = find_child(key, name, value);
787 if(res) {
788 return *res;
789 }
790 throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
791}
792
793const config& config::find_mandatory_child(std::string_view key, const std::string &name, const std::string &value) const
794{
795 auto res = find_child(key, name, value);
796 if(res) {
797 return *res;
798 }
799 throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
800}
801
803{
804 // No validity check for this function.
805 children_.clear();
806 values_.clear();
807 ordered_children_.clear();
808}
809
811{
812 // No validity check for this function.
813 children_.clear();
814 ordered_children_.clear();
815}
816
818{
819 // No validity check for this function.
820 values_.clear();
821}
822
823bool config::empty() const
824{
825 return children_.empty() && values_.empty();
826}
827
829{
830 return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
831}
832
834{
835 return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
836}
837
842
847
852
857
865
870
875
883
885{
886 config res;
887 get_diff(c, res);
888 return res;
889}
890
891void config::get_diff(const config& c, config& res) const
892{
893 config* inserts = nullptr;
894
895 for(const auto& [key, value] : values_) {
896 if(value.blank()) {
897 continue;
898 }
899
900 const attribute_map::const_iterator j = c.values_.find(key);
901 if(j == c.values_.end() || (value != j->second && !value.blank())) {
902 if(inserts == nullptr) {
903 inserts = &res.add_child("insert");
904 }
905
906 (*inserts)[key] = value;
907 }
908 }
909
910 config* deletes = nullptr;
911
912 for(const auto& [key, value] : c.values_) {
913 if(value.blank()) {
914 continue;
915 }
916
917 const attribute_map::const_iterator itor = values_.find(key);
918 if(itor == values_.end() || itor->second.blank()) {
919 if(deletes == nullptr) {
920 deletes = &res.add_child("delete");
921 }
922
923 (*deletes)[key] = "x";
924 }
925 }
926
927 std::vector<std::string> entities;
928
929 for(const auto& child : children_) {
930 entities.push_back(child.first);
931 }
932
933 for(const auto& child : c.children_) {
934 if(children_.count(child.first) == 0) {
935 entities.push_back(child.first);
936 }
937 }
938
939 for(const std::string& entity : entities) {
940 const child_map::const_iterator itor_a = children_.find(entity);
941 const child_map::const_iterator itor_b = c.children_.find(entity);
942
943 static const child_list dummy;
944
945 // Get the two child lists. 'b' has to be modified to look like 'a'.
946 const child_list& a = itor_a != children_.end() ? itor_a->second : dummy;
947 const child_list& b = itor_b != c.children_.end() ? itor_b->second : dummy;
948
949 std::size_t ndeletes = 0;
950 std::size_t ai = 0, bi = 0;
951 while(ai != a.size() || bi != b.size()) {
952 // If the two elements are the same, nothing needs to be done.
953 if(ai < a.size() && bi < b.size() && *a[ai] == *b[bi]) {
954 ++ai;
955 ++bi;
956 } else {
957 // We have to work out what the most appropriate operation --
958 // delete, insert, or change is the best to get b[bi] looking like a[ai].
959 std::stringstream buf;
960
961 // If b has more elements than a, then we assume this element
962 // is an element that needs deleting.
963 if(b.size() - bi > a.size() - ai) {
964 config& new_delete = res.add_child("delete_child");
965 buf << bi - ndeletes;
966 new_delete.values_["index"] = buf.str();
967 new_delete.add_child(entity);
968
969 ++ndeletes;
970 ++bi;
971 }
972
973 // If b has less elements than a, then we assume this element
974 // is an element that needs inserting.
975 else if(b.size() - bi < a.size() - ai) {
976 config& new_insert = res.add_child("insert_child");
977 buf << ai;
978 new_insert.values_["index"] = buf.str();
979 new_insert.add_child(entity, *a[ai]);
980
981 ++ai;
982 }
983
984 // Otherwise, they have the same number of elements,
985 // so try just changing this element to match.
986 else {
987 config& new_change = res.add_child("change_child");
988 buf << bi;
989 new_change.values_["index"] = buf.str();
990 new_change.add_child(entity, a[ai]->get_diff(*b[bi]));
991
992 ++ai;
993 ++bi;
994 }
995 }
996 }
997 }
998}
999
1000void config::apply_diff(const config& diff, bool track /* = false */)
1001{
1002 if(track) {
1003 values_[diff_track_attribute] = "modified";
1004 }
1005
1006 if(const auto inserts = diff.optional_child("insert")) {
1007 for(const auto& [key, value] : inserts->attribute_range()) {
1008 values_[key] = value;
1009 }
1010 }
1011
1012 if(const auto deletes = diff.optional_child("delete")) {
1013 for(const attribute& v : deletes->attribute_range()) {
1014 values_.erase(v.first);
1015 }
1016 }
1017
1018 for(const config& i : diff.child_range("change_child")) {
1019 const std::size_t index = i["index"].to_size_t();
1020 for(const auto [key, cfg] : i.all_children_view()) {
1021 if(key.empty()) {
1022 continue;
1023 }
1024
1025 const child_map::iterator itor = children_.find(key);
1026 if(itor == children_.end() || index >= itor->second.size()) {
1027 throw error("error in diff: could not find element '" + key + "'");
1028 }
1029
1030 itor->second[index]->apply_diff(cfg, track);
1031 }
1032 }
1033
1034 for(const config& i : diff.child_range("insert_child")) {
1035 const std::size_t index = i["index"].to_size_t();
1036 for(const auto [key, cfg] : i.all_children_view()) {
1037 config& inserted = add_child_at(key, cfg, index);
1038 if(track) {
1039 inserted[diff_track_attribute] = "new";
1040 }
1041 }
1042 }
1043
1044 for(const config& i : diff.child_range("delete_child")) {
1045 const std::size_t index = i["index"].to_size_t();
1046 for(const auto [key, cfg] : i.all_children_view()) {
1047 if(!track) {
1048 remove_child(key, index);
1049 } else {
1050 const child_map::iterator itor = children_.find(key);
1051 if(itor == children_.end() || index >= itor->second.size()) {
1052 throw error("error in diff: could not find element '" + key + "'");
1053 }
1054
1055 itor->second[index]->values_[diff_track_attribute] = "deleted";
1056 }
1057 }
1058 }
1059}
1060
1062{
1064 for(const config& i : diff.child_range("delete_child")) {
1065 const std::size_t index = i["index"].to_size_t();
1066 for(const auto [key, cfg] : i.all_children_view()) {
1067 remove_child(key, index);
1068 }
1069 }
1070
1071 for(const config& i : diff.child_range("change_child")) {
1072 const std::size_t index = i["index"].to_size_t();
1073 for(const auto [key, cfg] : i.all_children_view()) {
1074 if(key.empty()) {
1075 continue;
1076 }
1077
1078 const child_map::iterator itor = children_.find(key);
1079 if(itor == children_.end() || index >= itor->second.size()) {
1080 throw error("error in diff: could not find element '" + key + "'");
1081 }
1082
1083 itor->second[index]->clear_diff_track(cfg);
1084 }
1085 }
1086
1087 for(std::pair<const std::string, child_list>& p : children_) {
1088 for(auto& cfg : p.second) {
1090 }
1091 }
1092}
1093
1094/**
1095 * Merge config 'c' into this config, overwriting this config's values.
1096 */
1098{
1099 std::vector<child_pos> to_remove;
1100 std::map<std::string, unsigned> visitations;
1101
1102 // Merge attributes first
1104
1105 // Now merge shared tags
1107 for(i = ordered_children_.begin(); i != i_end; ++i) {
1108 const std::string& tag = i->pos->first;
1109 const child_map::const_iterator j = c.children_.find(tag);
1110
1111 if(j != c.children_.end()) {
1112 unsigned& visits = visitations[tag];
1113
1114 if(visits < j->second.size()) {
1115 // Get a const config so we do not add attributes.
1116 const config& merge_child = *j->second[visits++];
1117
1118 if(merge_child["__remove"].to_bool()) {
1119 to_remove.push_back(*i);
1120 } else {
1121 (i->pos->second[i->index])->merge_with(merge_child);
1122 }
1123 }
1124 }
1125 }
1126
1127 // Now add any unvisited tags
1128 for(const auto& [tag, list] : c.children_) {
1129 unsigned& visits = visitations[tag];
1130 while(visits < list.size()) {
1131 add_child(tag, *list[visits++]);
1132 }
1133 }
1134
1135 // Remove those marked so
1136 std::map<std::string, std::size_t> removals;
1137 for(const child_pos& pos : to_remove) {
1138 const std::string& tag = pos.pos->first;
1139 auto& removes = removals[tag];
1140 remove_child(tag, pos.index - removes++);
1141 }
1142}
1143
1144/**
1145 * Merge config 'c' into this config, preserving this config's values.
1146 */
1148{
1149 // Using a scratch config and merge_with() seems to execute about as fast
1150 // as direct coding of this merge.
1151 config scratch(c);
1152 scratch.merge_with(*this);
1153 swap(scratch);
1154}
1155
1156/**
1157 * Merge the attributes of config 'c' into this config, preserving this config's values.
1158 */
1160{
1161 for(const auto& [key, value] : cfg.values_) {
1162 attribute_value& v2 = values_[key];
1163 if(v2.blank()) {
1164 v2 = value;
1165 }
1166 }
1167}
1168bool config::matches(const config& filter) const
1169{
1170 bool result = true;
1171
1172 for(const auto& [key, value] : filter.attribute_range()) {
1173 if(key.compare(0, 8, "glob_on_") == 0) {
1174 const attribute_value* v = get(key.substr(8));
1175 if(!v || !utils::wildcard_string_match(v->str(), value.str())) {
1176 result = false;
1177 break;
1178 }
1179 } else {
1180 const attribute_value* v = get(key);
1181 if(!v || *v != value) {
1182 result = false;
1183 break;
1184 }
1185 }
1186 }
1187
1188 for(const auto [key, cfg] : filter.all_children_view()) {
1189 if(key == "not") {
1190 result = result && !matches(cfg);
1191 continue;
1192 } else if(key == "and") {
1193 result = result && matches(cfg);
1194 continue;
1195 } else if(key == "or") {
1196 result = result || matches(cfg);
1197 continue;
1198 }
1199
1200 bool found = false;
1201 for(const config& j : child_range(key)) {
1202 if(j.matches(cfg)) {
1203 found = true;
1204 break;
1205 }
1206 }
1207
1208 result = result && found;
1209 }
1210
1211 return result;
1212}
1213
1214std::string config::debug() const
1215{
1216 std::ostringstream outstream;
1217 outstream << *this;
1218 return outstream.str();
1219}
1220
1221std::ostream& operator<<(std::ostream& outstream, const config& cfg)
1222{
1223 io::write(outstream, cfg);
1224 return outstream;
1225}
1226
1227std::string config::hash() const
1228{
1229 static const unsigned int hash_length = 128;
1230 static const char hash_string[] = "+-,.<>0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1231 char hash_str[hash_length + 1];
1232
1233 unsigned int i;
1234 for(i = 0; i != hash_length; ++i) {
1235 hash_str[i] = 'a';
1236 }
1237
1238 hash_str[hash_length] = 0;
1239
1240 i = 0;
1241 for(const auto& [key, value] : values_) {
1242 if(value.blank()) {
1243 continue;
1244 }
1245
1246 for(char c : key) {
1247 hash_str[i] ^= c;
1248 if(++i == hash_length) {
1249 i = 0;
1250 }
1251 }
1252
1253 std::string base_str = value.t_str().base_str();
1254 for(const char c : base_str) {
1255 hash_str[i] ^= c;
1256 if(++i == hash_length) {
1257 i = 0;
1258 }
1259 }
1260 }
1261
1262 for(const auto [key, cfg] : all_children_view()) {
1263 std::string child_hash = cfg.hash();
1264 for(char c : child_hash) {
1265 hash_str[i] ^= c;
1266 ++i;
1267 if(i == hash_length) {
1268 i = 0;
1269 }
1270 }
1271 }
1272
1273 for(i = 0; i != hash_length; ++i) {
1274 hash_str[i] = hash_string[static_cast<unsigned>(hash_str[i]) % strlen(hash_string)];
1275 }
1276
1277 return std::string(hash_str);
1278}
1279
1280void config::swap(config& cfg) noexcept
1281{
1282 values_.swap(cfg.values_);
1283 children_.swap(cfg.children_);
1285}
1286
1287void swap(config& lhs, config& rhs) noexcept
1288{
1289 lhs.swap(rhs);
1290}
1291
1293{
1294 return std::all_of(children_.begin(), children_.end(), [](const auto& pair)
1295 {
1296 return valid_tag(pair.first) &&
1297 std::all_of(pair.second.begin(), pair.second.end(),
1298 [](const auto& c) { return c->validate_wml(); });
1299 }) &&
1300 std::all_of(values_.begin(), values_.end(), [](const auto& pair)
1301 {
1302 return valid_attribute(pair.first);
1303 });
1304}
1305
1306bool operator==(const config& a, const config& b)
1307{
1308 if(a.values_ != b.values_) {
1309 return false;
1310 }
1311
1312 config::const_all_children_itors x = a.all_children_range(), y = b.all_children_range();
1313 for(; !x.empty() && !y.empty(); x.pop_front(), y.pop_front()) {
1314 if(x.front().key != y.front().key || x.front().cfg != y.front().cfg) {
1315 return false;
1316 }
1317 }
1318
1319 return x.empty() && y.empty();
1320}
static auto & dummy
static unsigned int hash_str(const std::string &str)
Definition builder.cpp:1101
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool blank() const
Tests for an attribute that was never set.
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void remove_attribute(std::string_view key)
Definition config.cpp:162
static bool valid_tag(std::string_view name)
Definition config.cpp:129
config & add_child(std::string_view key)
Definition config.cpp:436
void merge_children_by_attribute(std::string_view key, std::string_view attribute)
All children with the given key and with equal values of the specified attribute will be merged into ...
Definition config.cpp:250
void append(const config &cfg)
Append data from another config object to this one.
Definition config.cpp:188
void insert(std::string_view key, T &&value)
Inserts an attribute into the config.
Definition config.hpp:519
const attribute_value & get_or(const std::string_view key, const std::string_view default_key) const
Chooses a value.
Definition config.cpp:671
all_children_iterator erase(const all_children_iterator &i)
Definition config.cpp:618
const_all_children_iterator ordered_begin() const
Definition config.cpp:838
void merge_children(std::string_view key)
All children with the given key will be merged into the first element with that key.
Definition config.cpp:235
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition config.hpp:762
std::size_t attribute_count() const
Count the number of non-blank attributes.
Definition config.cpp:307
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition config.cpp:380
bool matches(const config &filter) const
Definition config.cpp:1168
const attribute_value & get_old_attribute(std::string_view key, const std::string &old_key, const std::string &in_tag, const std::string &message="") const
Function to handle backward compatibility Get the value of key and if missing try old_key and log a d...
Definition config.cpp:688
void append_children_by_move(config &cfg, std::string_view key)
Moves children with the given name from the given config to this one.
Definition config.cpp:224
void recursive_clear_value(std::string_view key)
Definition config.cpp:583
const_attr_itors attribute_range() const
Definition config.cpp:740
attribute_map values_
All the attributes of this node.
Definition config.hpp:915
bool validate_wml() const
Returns true if this object represents valid WML, i.e.
Definition config.cpp:1292
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
Definition config.cpp:401
auto all_children_view() const
In-order iteration over all children.
Definition config.hpp:795
void merge_attributes(const config &)
Definition config.cpp:722
const_all_children_iterator ordered_end() const
Definition config.cpp:848
boost::iterator_range< child_iterator > child_itors
Definition config.hpp:280
config & find_mandatory_child(std::string_view key, const std::string &name, const std::string &value)
Definition config.cpp:784
std::vector< std::unique_ptr< config > > child_list
Definition config.hpp:190
const attribute_value & get_deprecated_attribute(std::string_view old_key, const std::string &in_tag, DEP_LEVEL level, const std::string &message) const
Get a deprecated attribute without a direct substitute, and log a deprecation message.
Definition config.cpp:710
child_itors child_range(std::string_view key)
Definition config.cpp:268
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition config.cpp:665
boost::iterator_range< attribute_iterator > attr_itors
Definition config.hpp:358
const_all_children_iterator ordered_cbegin() const
Definition config.cpp:843
static bool valid_attribute(std::string_view name)
Definition config.cpp:152
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition config.cpp:1097
config & add_child_at_total(std::string_view key, const config &val, std::size_t pos)
Definition config.cpp:512
void swap(config &cfg) noexcept
Definition config.cpp:1280
optional_config_impl< config > find_child(std::string_view key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition config.cpp:764
void clear_all_children()
Definition config.cpp:810
void inherit_from(const config &c)
Merge config 'c' into this config, preserving this config's values.
Definition config.cpp:1147
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition config.cpp:858
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition config.hpp:816
boost::iterator_range< all_children_iterator > all_children_itors
Definition config.hpp:761
void append_attributes(const config &cfg)
Adds attributes from cfg.
Definition config.cpp:174
boost::iterator_range< const_attribute_iterator > const_attr_itors
Definition config.hpp:357
void splice_children(config &src, std::string_view key)
Moves all the children with tag key from src to this.
Definition config.cpp:558
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition config.cpp:1000
~config()
Definition config.cpp:99
std::size_t all_children_count() const
Definition config.cpp:302
child_map children_
A list of all children of this node.
Definition config.hpp:918
attribute_value & operator[](std::string_view key)
Returns a reference to the attribute with the given key.
Definition config.cpp:677
const_all_children_iterator ordered_cend() const
Definition config.cpp:853
bool has_attribute(std::string_view key) const
Definition config.cpp:157
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object.
Definition config.cpp:884
config()
Definition config.cpp:76
std::string debug() const
Definition config.cpp:1214
std::vector< child_pos > ordered_children_
Definition config.hpp:920
attribute_map::value_type attribute
Definition config.hpp:297
void inherit_attributes(const config &c)
Merge the attributes of config 'c' into this config, preserving this config's values.
Definition config.cpp:1159
void remove_child(std::string_view key, std::size_t index)
Definition config.cpp:623
std::size_t child_count(std::string_view key) const
Definition config.cpp:292
optional_config_impl< const config > get_deprecated_child(std::string_view old_key, const std::string &in_tag, DEP_LEVEL level, const std::string &message) const
Get a deprecated child and log a deprecation message.
Definition config.cpp:411
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition config.cpp:312
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition config.cpp:1061
const config & child_or_empty(std::string_view key) const
Returns the first child with the given key, or an empty config if there is none.
Definition config.cpp:390
config & add_child_at(std::string_view key, const config &val, std::size_t index)
Definition config.cpp:465
std::size_t find_total_first_of(std::string_view key, std::size_t start=0)
Definition config.cpp:498
boost::iterator_range< const_child_iterator > const_child_itors
Definition config.hpp:281
void append_children(const config &cfg)
Adds children from cfg.
Definition config.cpp:167
void clear_attributes()
Definition config.cpp:817
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
void clear()
Definition config.cpp:802
void clear_children_impl(std::string_view key)
Definition config.cpp:548
std::string hash() const
Definition config.cpp:1227
friend bool operator==(const config &a, const config &b)
Definition config.cpp:1306
config & operator=(const config &)
Definition config.cpp:104
const_child_itors get_deprecated_child_range(std::string_view old_key, const std::string &in_tag, DEP_LEVEL level, const std::string &message) const
Get a deprecated child range and log a deprecation message.
Definition config.cpp:422
void remove_children(std::string_view key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition config.cpp:634
std::ostringstream wrapper.
Definition formatter.hpp:40
#define DBG_CF
Definition config.cpp:38
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition config.cpp:1287
static lg::log_domain log_wml("wml")
#define ERR_CF
Definition config.cpp:37
static lg::log_domain log_config("config")
Definitions for the interface to Wesnoth Markup Language (WML).
std::ostream & operator<<(std::ostream &, const config &)
Definition config.cpp:1221
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
const config * cfg
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition actions.cpp:59
void write(std::ostream &out, const configr_of &cfg, unsigned int level, bool strong_quotes)
Definition parser.cpp:748
auto find(Container &container, const Value &value, const Projection &projection={})
Definition general.hpp:213
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
std::vector< child_pos >::iterator Itor
Definition config.hpp:662
reference operator*() const
Definition config.cpp:828
const child_map::key_type & key
Definition config.hpp:642
child_map::iterator pos
Definition config.hpp:633
constexpr point size() const
Definition rect.hpp:67
mock_party p
static map_location::direction n
#define i
#define c
#define b