The Battle for Wesnoth  1.19.14+dev
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 "lexical_cast.hpp"
26 #include "log.hpp"
27 #include "deprecation.hpp"
28 #include "game_version.hpp"
30 #include "utils/general.hpp"
31 
32 #include <algorithm>
33 #include <cstring>
34 #include <iterator>
35 
36 static 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 
40 static lg::log_domain log_wml("wml");
41 #define ERR_WML LOG_STREAM(err, log_wml)
42 
43 namespace
44 {
45 // std::map::operator[] does not support heterogeneous lookup so we need this to work around.
46 template<typename Map, typename Key>
47 typename 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.
59 template<typename Map, typename Key>
60 int 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 
74 const 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 
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 
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 
153 {
154  return valid_tag(name);
155 }
156 
158 {
159  return values_.find(key) != values_.end();
160 }
161 
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 
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 
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 
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 
246  clear_children_impl(key);
247  add_child(key, std::move(merged_children));
248 }
249 
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 
262  clear_children_impl(key);
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 
292 std::size_t config::child_count(config_key_type 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 
302 std::size_t config::all_children_count() const
303 {
304  return ordered_children.size();
305 }
306 
307 std::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 
313 {
314  child_map::const_iterator i = children_.find(key);
315  return i != children_.end() && !i->second.empty();
316 }
317 
318 namespace {
319 template<class Tchildren>
320 auto get_child_impl(Tchildren& children, config_key_type 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 
344 config& config::mandatory_child(config_key_type 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 
353 const config& config::mandatory_child(config_key_type 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 
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 
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 
381 {
382  return get_child_impl(children_, key, n);
383 }
384 
386 {
387  return get_child_impl(children_, key, n);
388 }
389 
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 
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 
411 optional_config_impl<const config> config::get_deprecated_child(config_key_type 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 
422 config::const_child_itors config::get_deprecated_child_range(config_key_type 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 
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 
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 
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 
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 
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 
498 std::size_t config::find_total_first_of(config_key_type 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 
512 config& config::add_child_at_total(config_key_type 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 
548 namespace
549 {
550 struct remove_ordered
551 {
552  remove_ordered(const config::child_map::iterator& iter)
553  : iter_(iter)
554  {
555  }
556 
557  bool operator()(const config::child_pos& pos) const
558  {
559  return pos.pos == iter_;
560  }
561 
562 private:
564 };
565 } // end anon namespace
566 
568 {
569  child_map::iterator i = children_.find(key);
570  if(i == children_.end())
571  return;
572 
573  utils::erase_if(ordered_children, remove_ordered{i});
574  children_.erase(i);
575 }
576 
578 {
579  child_map::iterator i_src = src.children_.find(key);
580  if(i_src == src.children_.end()) {
581  return;
582  }
583 
584  utils::erase_if(src.ordered_children, remove_ordered{i_src});
585 
586  auto i_dst = map_get(children_, key);
587  child_list& dst = i_dst->second;
588 
589  const auto before = dst.size();
590  dst.insert(dst.end(), std::make_move_iterator(i_src->second.begin()), std::make_move_iterator(i_src->second.end()));
591  src.children_.erase(i_src);
592  // key might be a reference to i_src->first, so it is no longer usable.
593 
594  for(std::size_t j = before; j < dst.size(); ++j) {
595  ordered_children.emplace_back(i_dst, j);
596  }
597 }
598 
600 {
601  map_erase_key(values_, key);
602 
603  for(std::pair<const std::string, child_list>& p : children_) {
604  for(auto& cfg : p.second) {
606  }
607  }
608 }
609 
611 {
612  /* Find the position with the correct index and decrement all the
613  indices in the ordering that are above this index. */
614  std::size_t found = 0;
615  for(child_pos& p : ordered_children) {
616  if(p.pos != pos) {
617  continue;
618  }
619 
620  if(p.index == index) {
621  found = &p - &ordered_children.front();
622  } else if(p.index > index) {
623  --p.index;
624  }
625  }
626 
627  // Remove from the child map.
628  pos->second.erase(pos->second.begin() + index);
629 
630  // Erase from the ordering and return the next position.
631  return ordered_children.erase(ordered_children.begin() + found);
632 }
633 
635 {
636  return all_children_iterator(remove_child(i.i_->pos, i.i_->index));
637 }
638 
640 {
641  child_map::iterator i = children_.find(key);
642  if(i == children_.end() || index >= i->second.size()) {
643  ERR_CF << "Error: attempting to delete non-existing child: " << key << "[" << index << "]";
644  return;
645  }
646 
647  remove_child(i, index);
648 }
649 
650 void config::remove_children(config_key_type key, const std::function<bool(const config&)>& p)
651 {
652  child_map::iterator pos = children_.find(key);
653  if(pos == children_.end()) {
654  return;
655  }
656 
657  const auto predicate = [p](const std::unique_ptr<config>& child)
658  {
659  return !p || p(*child);
660  };
661 
662  auto child_it = std::find_if(pos->second.begin(), pos->second.end(), predicate);
663  while(child_it != pos->second.end()) {
664  const auto index = std::distance(pos->second.begin(), child_it);
665  remove_child(pos, index);
666  child_it = std::find_if(pos->second.begin() + index, pos->second.end(), predicate);
667  }
668 }
669 
671 {
672  const attribute_map::const_iterator i = values_.find(key);
673  if(i != values_.end()) {
674  return i->second;
675  }
676 
677  static const attribute_value empty_attribute;
678  return empty_attribute;
679 }
680 
682 {
683  attribute_map::const_iterator i = values_.find(key);
684  return i != values_.end() ? &i->second : nullptr;
685 }
686 
687 const config::attribute_value& config::get_or(const config_key_type key, const config_key_type default_key) const
688 {
689  const config::attribute_value & value = operator[](key);
690  return !value.blank() ? value : operator[](default_key);
691 }
692 
694 {
695  auto res = values_.lower_bound(key);
696 
697  if(res == values_.end() || key != res->first) {
698  res = values_.emplace_hint(res, std::piecewise_construct, std::forward_as_tuple(key), std::tuple<>());
699  }
700 
701  return res->second;
702 }
703 
704 const config::attribute_value& config::get_old_attribute(config_key_type key, const std::string& old_key, const std::string& in_tag, const std::string& message) const
705 {
706  if(has_attribute(old_key)) {
707  const std::string what = formatter() << "[" << in_tag << "]" << old_key << "=";
708  const std::string msg = formatter() << "Use " << key << "= instead. " << message;
710  }
711 
712  attribute_map::const_iterator i = values_.find(key);
713  if(i != values_.end()) {
714  return i->second;
715  }
716 
717  i = values_.find(old_key);
718  if(i != values_.end()) {
719  return i->second;
720  }
721 
722  static const attribute_value empty_attribute;
723  return empty_attribute;
724 }
725 
726 const config::attribute_value& config::get_deprecated_attribute(config_key_type old_key, const std::string& in_tag, DEP_LEVEL level, const std::string& message) const
727 {
728  if(auto i = values_.find(old_key); i != values_.end()) {
729  const std::string what = formatter() << "[" << in_tag << "]" << old_key << "=";
730  deprecated_message(what, level, "", message);
731  return i->second;
732  }
733 
734  static const attribute_value empty_attribute;
735  return empty_attribute;
736 }
737 
739 {
740  assert(this != &cfg);
741  for(const auto& [key, value] : cfg.values_) {
742  if(key.substr(0, 7) == "add_to_") {
743  std::string add_to = key.substr(7);
744  values_[add_to] = values_[add_to].to_double() + value.to_double();
745  } else if(key.substr(0, 10) == "concat_to_") {
746  std::string concat_to = key.substr(10);
747  // TODO: Only use t_string if one or both are actually translatable?
748  // That probably requires using a visitor though.
749  values_[concat_to] = values_[concat_to].t_str() + value.t_str();
750  } else {
751  values_[key] = value;
752  }
753  }
754 }
755 
757 {
759 
760  // Ensure the first element is not blank, as a few places assume this
761  while(range.begin() != range.end() && range.begin()->second.blank()) {
762  range.pop_front();
763  }
764 
765  return range;
766 }
767 
769 {
771 
772  // Ensure the first element is not blank, as a few places assume this
773  while(range.begin() != range.end() && range.begin()->second.blank()) {
774  range.pop_front();
775  }
776 
777  return range;
778 }
779 
780 optional_config config::find_child(config_key_type key, const std::string& name, const std::string& value)
781 {
782  const child_map::iterator i = children_.find(key);
783  if(i == children_.end()) {
784  DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
785  return utils::nullopt;
786  }
787 
788  const child_list::iterator j = utils::ranges::find(i->second, value,
789  [&](const std::unique_ptr<config>& pcfg) { return (*pcfg)[name]; });
790 
791  if(j != i->second.end()) {
792  return **j;
793  }
794 
795  DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
796 
797  return utils::nullopt;
798 }
799 
800 config& config::find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
801 {
802  auto res = find_child(key, name, value);
803  if(res) {
804  return *res;
805  }
806  throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
807 }
808 
809 const config& config::find_mandatory_child(config_key_type key, const std::string &name, const std::string &value) const
810 {
811  auto res = find_child(key, name, value);
812  if(res) {
813  return *res;
814  }
815  throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
816 }
817 
819 {
820  // No validity check for this function.
821  children_.clear();
822  values_.clear();
823  ordered_children.clear();
824 }
825 
827 {
828  // No validity check for this function.
829  children_.clear();
830  ordered_children.clear();
831 }
832 
834 {
835  // No validity check for this function.
836  values_.clear();
837 }
838 
839 bool config::empty() const
840 {
841  return children_.empty() && values_.empty();
842 }
843 
845 {
846  return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
847 }
848 
850 {
851  return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
852 }
853 
855 {
857 }
858 
860 {
862 }
863 
865 {
867 }
868 
870 {
872 }
873 
875 {
879  );
880 }
881 
883 {
884  return all_children_iterator(ordered_children.begin());
885 }
886 
888 {
890 }
891 
893 {
894  return all_children_itors(
897  );
898 }
899 
901 {
902  config res;
903  get_diff(c, res);
904  return res;
905 }
906 
907 void config::get_diff(const config& c, config& res) const
908 {
909  config* inserts = nullptr;
910 
911  for(const auto& [key, value] : values_) {
912  if(value.blank()) {
913  continue;
914  }
915 
916  const attribute_map::const_iterator j = c.values_.find(key);
917  if(j == c.values_.end() || (value != j->second && !value.blank())) {
918  if(inserts == nullptr) {
919  inserts = &res.add_child("insert");
920  }
921 
922  (*inserts)[key] = value;
923  }
924  }
925 
926  config* deletes = nullptr;
927 
928  for(const auto& [key, value] : c.values_) {
929  if(value.blank()) {
930  continue;
931  }
932 
933  const attribute_map::const_iterator itor = values_.find(key);
934  if(itor == values_.end() || itor->second.blank()) {
935  if(deletes == nullptr) {
936  deletes = &res.add_child("delete");
937  }
938 
939  (*deletes)[key] = "x";
940  }
941  }
942 
943  std::vector<std::string> entities;
944 
945  for(const auto& child : children_) {
946  entities.push_back(child.first);
947  }
948 
949  for(const auto& child : c.children_) {
950  if(children_.count(child.first) == 0) {
951  entities.push_back(child.first);
952  }
953  }
954 
955  for(const std::string& entity : entities) {
956  const child_map::const_iterator itor_a = children_.find(entity);
957  const child_map::const_iterator itor_b = c.children_.find(entity);
958 
959  static const child_list dummy;
960 
961  // Get the two child lists. 'b' has to be modified to look like 'a'.
962  const child_list& a = itor_a != children_.end() ? itor_a->second : dummy;
963  const child_list& b = itor_b != c.children_.end() ? itor_b->second : dummy;
964 
965  std::size_t ndeletes = 0;
966  std::size_t ai = 0, bi = 0;
967  while(ai != a.size() || bi != b.size()) {
968  // If the two elements are the same, nothing needs to be done.
969  if(ai < a.size() && bi < b.size() && *a[ai] == *b[bi]) {
970  ++ai;
971  ++bi;
972  } else {
973  // We have to work out what the most appropriate operation --
974  // delete, insert, or change is the best to get b[bi] looking like a[ai].
975  std::stringstream buf;
976 
977  // If b has more elements than a, then we assume this element
978  // is an element that needs deleting.
979  if(b.size() - bi > a.size() - ai) {
980  config& new_delete = res.add_child("delete_child");
981  buf << bi - ndeletes;
982  new_delete.values_["index"] = buf.str();
983  new_delete.add_child(entity);
984 
985  ++ndeletes;
986  ++bi;
987  }
988 
989  // If b has less elements than a, then we assume this element
990  // is an element that needs inserting.
991  else if(b.size() - bi < a.size() - ai) {
992  config& new_insert = res.add_child("insert_child");
993  buf << ai;
994  new_insert.values_["index"] = buf.str();
995  new_insert.add_child(entity, *a[ai]);
996 
997  ++ai;
998  }
999 
1000  // Otherwise, they have the same number of elements,
1001  // so try just changing this element to match.
1002  else {
1003  config& new_change = res.add_child("change_child");
1004  buf << bi;
1005  new_change.values_["index"] = buf.str();
1006  new_change.add_child(entity, a[ai]->get_diff(*b[bi]));
1007 
1008  ++ai;
1009  ++bi;
1010  }
1011  }
1012  }
1013  }
1014 }
1015 
1016 void config::apply_diff(const config& diff, bool track /* = false */)
1017 {
1018  if(track) {
1019  values_[diff_track_attribute] = "modified";
1020  }
1021 
1022  if(const auto inserts = diff.optional_child("insert")) {
1023  for(const auto& [key, value] : inserts->attribute_range()) {
1024  values_[key] = value;
1025  }
1026  }
1027 
1028  if(const auto deletes = diff.optional_child("delete")) {
1029  for(const attribute& v : deletes->attribute_range()) {
1030  values_.erase(v.first);
1031  }
1032  }
1033 
1034  for(const config& i : diff.child_range("change_child")) {
1035  const std::size_t index = lexical_cast<std::size_t>(i["index"].str());
1036  for(const auto [key, cfg] : i.all_children_view()) {
1037  if(key.empty()) {
1038  continue;
1039  }
1040 
1041  const child_map::iterator itor = children_.find(key);
1042  if(itor == children_.end() || index >= itor->second.size()) {
1043  throw error("error in diff: could not find element '" + key + "'");
1044  }
1045 
1046  itor->second[index]->apply_diff(cfg, track);
1047  }
1048  }
1049 
1050  for(const config& i : diff.child_range("insert_child")) {
1051  const auto index = lexical_cast<std::size_t>(i["index"].str());
1052  for(const auto [key, cfg] : i.all_children_view()) {
1053  config& inserted = add_child_at(key, cfg, index);
1054  if(track) {
1055  inserted[diff_track_attribute] = "new";
1056  }
1057  }
1058  }
1059 
1060  for(const config& i : diff.child_range("delete_child")) {
1061  const auto index = lexical_cast<std::size_t>(i["index"].str());
1062  for(const auto [key, cfg] : i.all_children_view()) {
1063  if(!track) {
1064  remove_child(key, index);
1065  } else {
1066  const child_map::iterator itor = children_.find(key);
1067  if(itor == children_.end() || index >= itor->second.size()) {
1068  throw error("error in diff: could not find element '" + key + "'");
1069  }
1070 
1071  itor->second[index]->values_[diff_track_attribute] = "deleted";
1072  }
1073  }
1074  }
1075 }
1076 
1078 {
1080  for(const config& i : diff.child_range("delete_child")) {
1081  const auto index = lexical_cast<std::size_t>(i["index"].str());
1082  for(const auto [key, cfg] : i.all_children_view()) {
1083  remove_child(key, index);
1084  }
1085  }
1086 
1087  for(const config& i : diff.child_range("change_child")) {
1088  const std::size_t index = lexical_cast<std::size_t>(i["index"].str());
1089  for(const auto [key, cfg] : i.all_children_view()) {
1090  if(key.empty()) {
1091  continue;
1092  }
1093 
1094  const child_map::iterator itor = children_.find(key);
1095  if(itor == children_.end() || index >= itor->second.size()) {
1096  throw error("error in diff: could not find element '" + key + "'");
1097  }
1098 
1099  itor->second[index]->clear_diff_track(cfg);
1100  }
1101  }
1102 
1103  for(std::pair<const std::string, child_list>& p : children_) {
1104  for(auto& cfg : p.second) {
1106  }
1107  }
1108 }
1109 
1110 /**
1111  * Merge config 'c' into this config, overwriting this config's values.
1112  */
1114 {
1115  std::vector<child_pos> to_remove;
1116  std::map<std::string, unsigned> visitations;
1117 
1118  // Merge attributes first
1120 
1121  // Now merge shared tags
1123  for(i = ordered_children.begin(); i != i_end; ++i) {
1124  const std::string& tag = i->pos->first;
1125  const child_map::const_iterator j = c.children_.find(tag);
1126 
1127  if(j != c.children_.end()) {
1128  unsigned& visits = visitations[tag];
1129 
1130  if(visits < j->second.size()) {
1131  // Get a const config so we do not add attributes.
1132  const config& merge_child = *j->second[visits++];
1133 
1134  if(merge_child["__remove"].to_bool()) {
1135  to_remove.push_back(*i);
1136  } else {
1137  (i->pos->second[i->index])->merge_with(merge_child);
1138  }
1139  }
1140  }
1141  }
1142 
1143  // Now add any unvisited tags
1144  for(const auto& [tag, list] : c.children_) {
1145  unsigned& visits = visitations[tag];
1146  while(visits < list.size()) {
1147  add_child(tag, *list[visits++]);
1148  }
1149  }
1150 
1151  // Remove those marked so
1152  std::map<std::string, std::size_t> removals;
1153  for(const child_pos& pos : to_remove) {
1154  const std::string& tag = pos.pos->first;
1155  auto& removes = removals[tag];
1156  remove_child(tag, pos.index - removes++);
1157  }
1158 }
1159 
1160 /**
1161  * Merge config 'c' into this config, preserving this config's values.
1162  */
1164 {
1165  // Using a scratch config and merge_with() seems to execute about as fast
1166  // as direct coding of this merge.
1167  config scratch(c);
1168  scratch.merge_with(*this);
1169  swap(scratch);
1170 }
1171 
1172 /**
1173  * Merge the attributes of config 'c' into this config, preserving this config's values.
1174  */
1176 {
1177  for(const auto& [key, value] : cfg.values_) {
1178  attribute_value& v2 = values_[key];
1179  if(v2.blank()) {
1180  v2 = value;
1181  }
1182  }
1183 }
1184 bool config::matches(const config& filter) const
1185 {
1186  bool result = true;
1187 
1188  for(const auto& [key, value] : filter.attribute_range()) {
1189  if(key.compare(0, 8, "glob_on_") == 0) {
1190  const attribute_value* v = get(key.substr(8));
1191  if(!v || !utils::wildcard_string_match(v->str(), value.str())) {
1192  result = false;
1193  break;
1194  }
1195  } else {
1196  const attribute_value* v = get(key);
1197  if(!v || *v != value) {
1198  result = false;
1199  break;
1200  }
1201  }
1202  }
1203 
1204  for(const auto [key, cfg] : filter.all_children_view()) {
1205  if(key == "not") {
1206  result = result && !matches(cfg);
1207  continue;
1208  } else if(key == "and") {
1209  result = result && matches(cfg);
1210  continue;
1211  } else if(key == "or") {
1212  result = result || matches(cfg);
1213  continue;
1214  }
1215 
1216  bool found = false;
1217  for(const config& j : child_range(key)) {
1218  if(j.matches(cfg)) {
1219  found = true;
1220  break;
1221  }
1222  }
1223 
1224  result = result && found;
1225  }
1226 
1227  return result;
1228 }
1229 
1230 std::string config::debug() const
1231 {
1232  std::ostringstream outstream;
1233  outstream << *this;
1234  return outstream.str();
1235 }
1236 
1237 std::ostream& operator<<(std::ostream& outstream, const config& cfg)
1238 {
1239  static int i = 0;
1240  i++;
1241 
1242  for(const auto& [key, value] : cfg.attribute_range()) {
1243  if(value.blank()) {
1244  continue;
1245  }
1246 
1247  for(int j = 0; j < i - 1; j++) {
1248  outstream << '\t';
1249  }
1250 
1251  outstream << key << " = " << value << '\n';
1252  }
1253 
1254  for(const auto [key, cfg] : cfg.all_children_view()) {
1255  for(int j = 0; j < i - 1; ++j) {
1256  outstream << '\t';
1257  }
1258 
1259  outstream << "[" << key << "]\n";
1260  outstream << cfg;
1261 
1262  for(int j = 0; j < i - 1; ++j) {
1263  outstream << '\t';
1264  }
1265 
1266  outstream << "[/" << key << "]\n";
1267  }
1268 
1269  i--;
1270  return outstream;
1271 }
1272 
1273 std::string config::hash() const
1274 {
1275  static const unsigned int hash_length = 128;
1276  static const char hash_string[] = "+-,.<>0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1277  char hash_str[hash_length + 1];
1278 
1279  unsigned int i;
1280  for(i = 0; i != hash_length; ++i) {
1281  hash_str[i] = 'a';
1282  }
1283 
1284  hash_str[hash_length] = 0;
1285 
1286  i = 0;
1287  for(const auto& [key, value] : values_) {
1288  if(value.blank()) {
1289  continue;
1290  }
1291 
1292  for(char c : key) {
1293  hash_str[i] ^= c;
1294  if(++i == hash_length) {
1295  i = 0;
1296  }
1297  }
1298 
1299  std::string base_str = value.t_str().base_str();
1300  for(const char c : base_str) {
1301  hash_str[i] ^= c;
1302  if(++i == hash_length) {
1303  i = 0;
1304  }
1305  }
1306  }
1307 
1308  for(const auto [key, cfg] : all_children_view()) {
1309  std::string child_hash = cfg.hash();
1310  for(char c : child_hash) {
1311  hash_str[i] ^= c;
1312  ++i;
1313  if(i == hash_length) {
1314  i = 0;
1315  }
1316  }
1317  }
1318 
1319  for(i = 0; i != hash_length; ++i) {
1320  hash_str[i] = hash_string[static_cast<unsigned>(hash_str[i]) % strlen(hash_string)];
1321  }
1322 
1323  return std::string(hash_str);
1324 }
1325 
1327 {
1328  values_.swap(cfg.values_);
1329  children_.swap(cfg.children_);
1331 }
1332 
1333 void swap(config& lhs, config& rhs)
1334 {
1335  lhs.swap(rhs);
1336 }
1337 
1339 {
1340  return std::all_of(children_.begin(), children_.end(), [](const auto& pair)
1341  {
1342  return valid_tag(pair.first) &&
1343  std::all_of(pair.second.begin(), pair.second.end(),
1344  [](const auto& c) { return c->validate_wml(); });
1345  }) &&
1346  std::all_of(values_.begin(), values_.end(), [](const auto& pair)
1347  {
1348  return valid_attribute(pair.first);
1349  });
1350 }
1351 
1352 bool operator==(const config& a, const config& b)
1353 {
1354  if(a.values_ != b.values_) {
1355  return false;
1356  }
1357 
1358  config::const_all_children_itors x = a.all_children_range(), y = b.all_children_range();
1359  for(; !x.empty() && !y.empty(); x.pop_front(), y.pop_front()) {
1360  if(x.front().key != y.front().key || x.front().cfg != y.front().cfg) {
1361  return false;
1362  }
1363  }
1364 
1365  return x.empty() && y.empty();
1366 }
static auto & dummy
static unsigned int hash_str(const std::string &str)
Definition: builder.cpp:1102
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:158
void merge_children_by_attribute(config_key_type key, config_key_type attribute)
All children with the given key and with equal values of the specified attribute will be merged into ...
Definition: config.cpp:250
const attribute_value & get_old_attribute(config_key_type 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:704
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:188
const attribute_value & get_or(const config_key_type key, const config_key_type default_key) const
Chooses a value.
Definition: config.cpp:687
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:634
const_all_children_iterator ordered_begin() const
Definition: config.cpp:854
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:763
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:390
std::size_t attribute_count() const
Count the number of non-blank attributes.
Definition: config.cpp:307
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:362
attribute_value & operator[](config_key_type key)
Returns a reference to the attribute with the given key.
Definition: config.cpp:693
void recursive_clear_value(config_key_type key)
Definition: config.cpp:599
bool matches(const config &filter) const
Definition: config.cpp:1184
void remove_child(config_key_type key, std::size_t index)
Definition: config.cpp:639
void merge_children(config_key_type key)
All children with the given key will be merged into the first element with that key.
Definition: config.cpp:235
const_attr_itors attribute_range() const
Definition: config.cpp:756
std::size_t child_count(config_key_type key) const
Definition: config.cpp:292
attribute_map values_
All the attributes of this node.
Definition: config.hpp:916
bool validate_wml() const
Returns true if this object represents valid WML, i.e.
Definition: config.cpp:1338
optional_config_impl< const config > get_deprecated_child(config_key_type 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
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
void merge_attributes(const config &)
Definition: config.cpp:738
const_all_children_iterator ordered_end() const
Definition: config.cpp:864
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:281
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:465
config & find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
Definition: config.cpp:800
const attribute_value & get_deprecated_attribute(config_key_type 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:726
std::size_t find_total_first_of(config_key_type key, std::size_t start=0)
Definition: config.cpp:498
std::vector< std::unique_ptr< config > > child_list
Definition: config.hpp:191
optional_config_impl< config > find_child(config_key_type 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:780
static bool valid_tag(config_key_type name)
Definition: config.cpp:129
boost::iterator_range< attribute_iterator > attr_itors
Definition: config.hpp:359
const_all_children_iterator ordered_cbegin() const
Definition: config.cpp:859
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1113
void clear_children_impl(config_key_type key)
Definition: config.cpp:567
const_child_itors get_deprecated_child_range(config_key_type 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(config_key_type key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:650
void clear_all_children()
Definition: config.cpp:826
void inherit_from(const config &c)
Merge config 'c' into this config, preserving this config's values.
Definition: config.cpp:1163
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:874
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition: config.hpp:817
boost::iterator_range< all_children_iterator > all_children_itors
Definition: config.hpp:762
child_itors child_range(config_key_type key)
Definition: config.cpp:268
void append_attributes(const config &cfg)
Adds attributes from cfg.
Definition: config.cpp:174
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:401
boost::iterator_range< const_attribute_iterator > const_attr_itors
Definition: config.hpp:358
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1016
~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:919
const_all_children_iterator ordered_cend() const
Definition: config.cpp:869
config & add_child_at_total(config_key_type key, const config &val, std::size_t pos)
Definition: config.cpp:512
void remove_attribute(config_key_type key)
Definition: config.cpp:162
static bool valid_attribute(config_key_type name)
Definition: config.cpp:152
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:900
config()
Definition: config.cpp:76
std::string debug() const
Definition: config.cpp:1230
attribute_map::value_type attribute
Definition: config.hpp:298
void inherit_attributes(const config &c)
Merge the attributes of config 'c' into this config, preserving this config's values.
Definition: config.cpp:1175
std::vector< child_pos > ordered_children
Definition: config.hpp:921
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition: config.cpp:1077
void swap(config &cfg)
Definition: config.cpp:1326
void append_children_by_move(config &cfg, config_key_type key)
Moves children with the given name from the given config to this one.
Definition: config.cpp:224
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:282
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:167
void clear_attributes()
Definition: config.cpp:833
bool empty() const
Definition: config.cpp:839
void splice_children(config &src, config_key_type key)
Moves all the children with tag key from src to this.
Definition: config.cpp:577
void clear()
Definition: config.cpp:818
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:681
std::string hash() const
Definition: config.cpp:1273
friend bool operator==(const config &a, const config &b)
Definition: config.cpp:1352
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & operator=(const config &)
Definition: config.cpp:104
config & add_child(config_key_type key)
Definition: config.cpp:436
std::ostringstream wrapper.
Definition: formatter.hpp:40
#define DBG_CF
Definition: config.cpp:38
std::ostream & operator<<(std::ostream &outstream, const config &cfg)
Definition: config.cpp:1237
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1333
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::string_view config_key_type
Definition: config.hpp:48
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
Definition: deprecation.hpp:21
const config * cfg
std::size_t i
Definition: function.cpp:1032
Interfaces for manipulating version numbers of engine, add-ons, etc.
New lexcical_cast header.
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 clear()
Clear the current render target.
Definition: draw.cpp:42
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
auto find(Container &container, const Value &value, const Projection &projection={})
Definition: general.hpp:179
constexpr auto filter
Definition: ranges.hpp:38
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:107
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
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:663
reference operator*() const
Definition: config.cpp:844
const child_map::key_type & key
Definition: config.hpp:643
child_map::iterator pos
Definition: config.hpp:634
constexpr point size() const
Definition: rect.hpp:67
mock_char c
mock_party p
static map_location::direction n
#define b