The Battle for Wesnoth  1.19.8+dev
config.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 {
88  append_children(cfg);
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 
188 void config::append(const config& cfg)
189 {
190  append_children(cfg);
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_);
201  ordered_children = std::move(cfg.ordered_children);
202  cfg.clear_all_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  }
208  cfg.clear_all_children();
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  }
221  cfg.clear_attributes();
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 
232  cfg.clear_children_impl(key);
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 
499 {
500  assert(start <= ordered_children.size());
501  const size_t npos = static_cast<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<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) {
605  cfg->recursive_clear_value(key);
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 
786 
787  return utils::nullopt;
788  }
789 
790  const child_list::iterator j = std::find_if(i->second.begin(), i->second.end(),
791  [&](const std::unique_ptr<config>& pcfg) {
792  const config& cfg = *pcfg;
793  return cfg[name] == value;
794  }
795  );
796 
797  if(j != i->second.end()) {
798  return **j;
799  }
800 
801  DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
802 
803  return utils::nullopt;
804 }
805 
806 config& config::find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
807 {
808  auto res = find_child(key, name, value);
809  if(res) {
810  return *res;
811  }
812  throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
813 }
814 
815 const config& config::find_mandatory_child(config_key_type key, const std::string &name, const std::string &value) const
816 {
817  auto res = find_child(key, name, value);
818  if(res) {
819  return *res;
820  }
821  throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
822 }
823 
825 {
826  // No validity check for this function.
827  children_.clear();
828  values_.clear();
829  ordered_children.clear();
830 }
831 
833 {
834  // No validity check for this function.
835  children_.clear();
836  ordered_children.clear();
837 }
838 
840 {
841  // No validity check for this function.
842  values_.clear();
843 }
844 
845 bool config::empty() const
846 {
847  return children_.empty() && values_.empty();
848 }
849 
851 {
852  return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
853 }
854 
856 {
857  return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
858 }
859 
861 {
863 }
864 
866 {
868 }
869 
871 {
873 }
874 
876 {
878 }
879 
881 {
885  );
886 }
887 
889 {
890  return all_children_iterator(ordered_children.begin());
891 }
892 
894 {
896 }
897 
899 {
900  return all_children_itors(
903  );
904 }
905 
907 {
908  config res;
909  get_diff(c, res);
910  return res;
911 }
912 
913 void config::get_diff(const config& c, config& res) const
914 {
915  config* inserts = nullptr;
916 
917  for(const auto& [key, value] : values_) {
918  if(value.blank()) {
919  continue;
920  }
921 
922  const attribute_map::const_iterator j = c.values_.find(key);
923  if(j == c.values_.end() || (value != j->second && !value.blank())) {
924  if(inserts == nullptr) {
925  inserts = &res.add_child("insert");
926  }
927 
928  (*inserts)[key] = value;
929  }
930  }
931 
932  config* deletes = nullptr;
933 
934  for(const auto& [key, value] : c.values_) {
935  if(value.blank()) {
936  continue;
937  }
938 
939  const attribute_map::const_iterator itor = values_.find(key);
940  if(itor == values_.end() || itor->second.blank()) {
941  if(deletes == nullptr) {
942  deletes = &res.add_child("delete");
943  }
944 
945  (*deletes)[key] = "x";
946  }
947  }
948 
949  std::vector<std::string> entities;
950 
951  for(const auto& child : children_) {
952  entities.push_back(child.first);
953  }
954 
955  for(const auto& child : c.children_) {
956  if(children_.count(child.first) == 0) {
957  entities.push_back(child.first);
958  }
959  }
960 
961  for(const std::string& entity : entities) {
962  const child_map::const_iterator itor_a = children_.find(entity);
963  const child_map::const_iterator itor_b = c.children_.find(entity);
964 
965  static const child_list dummy;
966 
967  // Get the two child lists. 'b' has to be modified to look like 'a'.
968  const child_list& a = itor_a != children_.end() ? itor_a->second : dummy;
969  const child_list& b = itor_b != c.children_.end() ? itor_b->second : dummy;
970 
971  std::size_t ndeletes = 0;
972  std::size_t ai = 0, bi = 0;
973  while(ai != a.size() || bi != b.size()) {
974  // If the two elements are the same, nothing needs to be done.
975  if(ai < a.size() && bi < b.size() && *a[ai] == *b[bi]) {
976  ++ai;
977  ++bi;
978  } else {
979  // We have to work out what the most appropriate operation --
980  // delete, insert, or change is the best to get b[bi] looking like a[ai].
981  std::stringstream buf;
982 
983  // If b has more elements than a, then we assume this element
984  // is an element that needs deleting.
985  if(b.size() - bi > a.size() - ai) {
986  config& new_delete = res.add_child("delete_child");
987  buf << bi - ndeletes;
988  new_delete.values_["index"] = buf.str();
989  new_delete.add_child(entity);
990 
991  ++ndeletes;
992  ++bi;
993  }
994 
995  // If b has less elements than a, then we assume this element
996  // is an element that needs inserting.
997  else if(b.size() - bi < a.size() - ai) {
998  config& new_insert = res.add_child("insert_child");
999  buf << ai;
1000  new_insert.values_["index"] = buf.str();
1001  new_insert.add_child(entity, *a[ai]);
1002 
1003  ++ai;
1004  }
1005 
1006  // Otherwise, they have the same number of elements,
1007  // so try just changing this element to match.
1008  else {
1009  config& new_change = res.add_child("change_child");
1010  buf << bi;
1011  new_change.values_["index"] = buf.str();
1012  new_change.add_child(entity, a[ai]->get_diff(*b[bi]));
1013 
1014  ++ai;
1015  ++bi;
1016  }
1017  }
1018  }
1019  }
1020 }
1021 
1022 void config::apply_diff(const config& diff, bool track /* = false */)
1023 {
1024  if(track) {
1025  values_[diff_track_attribute] = "modified";
1026  }
1027 
1028  if(const auto inserts = diff.optional_child("insert")) {
1029  for(const auto& [key, value] : inserts->attribute_range()) {
1030  values_[key] = value;
1031  }
1032  }
1033 
1034  if(const auto deletes = diff.optional_child("delete")) {
1035  for(const attribute& v : deletes->attribute_range()) {
1036  values_.erase(v.first);
1037  }
1038  }
1039 
1040  for(const config& i : diff.child_range("change_child")) {
1041  const std::size_t index = lexical_cast<std::size_t>(i["index"].str());
1042  for(const auto [key, cfg] : i.all_children_view()) {
1043  if(key.empty()) {
1044  continue;
1045  }
1046 
1047  const child_map::iterator itor = children_.find(key);
1048  if(itor == children_.end() || index >= itor->second.size()) {
1049  throw error("error in diff: could not find element '" + key + "'");
1050  }
1051 
1052  itor->second[index]->apply_diff(cfg, track);
1053  }
1054  }
1055 
1056  for(const config& i : diff.child_range("insert_child")) {
1057  const auto index = lexical_cast<std::size_t>(i["index"].str());
1058  for(const auto [key, cfg] : i.all_children_view()) {
1059  config& inserted = add_child_at(key, cfg, index);
1060  if(track) {
1061  inserted[diff_track_attribute] = "new";
1062  }
1063  }
1064  }
1065 
1066  for(const config& i : diff.child_range("delete_child")) {
1067  const auto index = lexical_cast<std::size_t>(i["index"].str());
1068  for(const auto [key, cfg] : i.all_children_view()) {
1069  if(!track) {
1070  remove_child(key, index);
1071  } else {
1072  const child_map::iterator itor = children_.find(key);
1073  if(itor == children_.end() || index >= itor->second.size()) {
1074  throw error("error in diff: could not find element '" + key + "'");
1075  }
1076 
1077  itor->second[index]->values_[diff_track_attribute] = "deleted";
1078  }
1079  }
1080  }
1081 }
1082 
1084 {
1086  for(const config& i : diff.child_range("delete_child")) {
1087  const auto index = lexical_cast<std::size_t>(i["index"].str());
1088  for(const auto [key, cfg] : i.all_children_view()) {
1089  remove_child(key, index);
1090  }
1091  }
1092 
1093  for(const config& i : diff.child_range("change_child")) {
1094  const std::size_t index = lexical_cast<std::size_t>(i["index"].str());
1095  for(const auto [key, cfg] : i.all_children_view()) {
1096  if(key.empty()) {
1097  continue;
1098  }
1099 
1100  const child_map::iterator itor = children_.find(key);
1101  if(itor == children_.end() || index >= itor->second.size()) {
1102  throw error("error in diff: could not find element '" + key + "'");
1103  }
1104 
1105  itor->second[index]->clear_diff_track(cfg);
1106  }
1107  }
1108 
1109  for(std::pair<const std::string, child_list>& p : children_) {
1110  for(auto& cfg : p.second) {
1111  cfg->remove_attribute(diff_track_attribute);
1112  }
1113  }
1114 }
1115 
1116 /**
1117  * Merge config 'c' into this config, overwriting this config's values.
1118  */
1120 {
1121  std::vector<child_pos> to_remove;
1122  std::map<std::string, unsigned> visitations;
1123 
1124  // Merge attributes first
1126 
1127  // Now merge shared tags
1129  for(i = ordered_children.begin(); i != i_end; ++i) {
1130  const std::string& tag = i->pos->first;
1131  const child_map::const_iterator j = c.children_.find(tag);
1132 
1133  if(j != c.children_.end()) {
1134  unsigned& visits = visitations[tag];
1135 
1136  if(visits < j->second.size()) {
1137  // Get a const config so we do not add attributes.
1138  const config& merge_child = *j->second[visits++];
1139 
1140  if(merge_child["__remove"].to_bool()) {
1141  to_remove.push_back(*i);
1142  } else {
1143  (i->pos->second[i->index])->merge_with(merge_child);
1144  }
1145  }
1146  }
1147  }
1148 
1149  // Now add any unvisited tags
1150  for(const auto& [tag, list] : c.children_) {
1151  unsigned& visits = visitations[tag];
1152  while(visits < list.size()) {
1153  add_child(tag, *list[visits++]);
1154  }
1155  }
1156 
1157  // Remove those marked so
1158  std::map<std::string, std::size_t> removals;
1159  for(const child_pos& pos : to_remove) {
1160  const std::string& tag = pos.pos->first;
1161  auto& removes = removals[tag];
1162  remove_child(tag, pos.index - removes++);
1163  }
1164 }
1165 
1166 /**
1167  * Merge config 'c' into this config, preserving this config's values.
1168  */
1170 {
1171  // Using a scratch config and merge_with() seems to execute about as fast
1172  // as direct coding of this merge.
1173  config scratch(c);
1174  scratch.merge_with(*this);
1175  swap(scratch);
1176 }
1177 
1178 /**
1179  * Merge the attributes of config 'c' into this config, preserving this config's values.
1180  */
1182 {
1183  for(const auto& [key, value] : cfg.values_) {
1184  attribute_value& v2 = values_[key];
1185  if(v2.blank()) {
1186  v2 = value;
1187  }
1188  }
1189 }
1190 bool config::matches(const config& filter) const
1191 {
1192  bool result = true;
1193 
1194  for(const auto& [key, value] : filter.attribute_range()) {
1195  if(key.compare(0, 8, "glob_on_") == 0) {
1196  const attribute_value* v = get(key.substr(8));
1197  if(!v || !utils::wildcard_string_match(v->str(), value.str())) {
1198  result = false;
1199  break;
1200  }
1201  } else {
1202  const attribute_value* v = get(key);
1203  if(!v || *v != value) {
1204  result = false;
1205  break;
1206  }
1207  }
1208  }
1209 
1210  for(const auto [key, cfg] : filter.all_children_view()) {
1211  if(key == "not") {
1212  result = result && !matches(cfg);
1213  continue;
1214  } else if(key == "and") {
1215  result = result && matches(cfg);
1216  continue;
1217  } else if(key == "or") {
1218  result = result || matches(cfg);
1219  continue;
1220  }
1221 
1222  bool found = false;
1223  for(const config& j : child_range(key)) {
1224  if(j.matches(cfg)) {
1225  found = true;
1226  break;
1227  }
1228  }
1229 
1230  result = result && found;
1231  }
1232 
1233  return result;
1234 }
1235 
1236 std::string config::debug() const
1237 {
1238  std::ostringstream outstream;
1239  outstream << *this;
1240  return outstream.str();
1241 }
1242 
1243 std::ostream& operator<<(std::ostream& outstream, const config& cfg)
1244 {
1245  static int i = 0;
1246  i++;
1247 
1248  for(const auto& [key, value] : cfg.attribute_range()) {
1249  if(value.blank()) {
1250  continue;
1251  }
1252 
1253  for(int j = 0; j < i - 1; j++) {
1254  outstream << '\t';
1255  }
1256 
1257  outstream << key << " = " << value << '\n';
1258  }
1259 
1260  for(const auto [key, cfg] : cfg.all_children_view()) {
1261  for(int j = 0; j < i - 1; ++j) {
1262  outstream << '\t';
1263  }
1264 
1265  outstream << "[" << key << "]\n";
1266  outstream << cfg;
1267 
1268  for(int j = 0; j < i - 1; ++j) {
1269  outstream << '\t';
1270  }
1271 
1272  outstream << "[/" << key << "]\n";
1273  }
1274 
1275  i--;
1276  return outstream;
1277 }
1278 
1279 std::string config::hash() const
1280 {
1281  static const unsigned int hash_length = 128;
1282  static const char hash_string[] = "+-,.<>0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1283  char hash_str[hash_length + 1];
1284 
1285  unsigned int i;
1286  for(i = 0; i != hash_length; ++i) {
1287  hash_str[i] = 'a';
1288  }
1289 
1290  hash_str[hash_length] = 0;
1291 
1292  i = 0;
1293  for(const auto& [key, value] : values_) {
1294  if(value.blank()) {
1295  continue;
1296  }
1297 
1298  for(char c : key) {
1299  hash_str[i] ^= c;
1300  if(++i == hash_length) {
1301  i = 0;
1302  }
1303  }
1304 
1305  std::string base_str = value.t_str().base_str();
1306  for(const char c : base_str) {
1307  hash_str[i] ^= c;
1308  if(++i == hash_length) {
1309  i = 0;
1310  }
1311  }
1312  }
1313 
1314  for(const auto [key, cfg] : all_children_view()) {
1315  std::string child_hash = cfg.hash();
1316  for(char c : child_hash) {
1317  hash_str[i] ^= c;
1318  ++i;
1319  if(i == hash_length) {
1320  i = 0;
1321  }
1322  }
1323  }
1324 
1325  for(i = 0; i != hash_length; ++i) {
1326  hash_str[i] = hash_string[static_cast<unsigned>(hash_str[i]) % strlen(hash_string)];
1327  }
1328 
1329  return std::string(hash_str);
1330 }
1331 
1333 {
1334  values_.swap(cfg.values_);
1335  children_.swap(cfg.children_);
1337 }
1338 
1339 void swap(config& lhs, config& rhs)
1340 {
1341  lhs.swap(rhs);
1342 }
1343 
1345 {
1346  return std::all_of(children_.begin(), children_.end(), [](const auto& pair)
1347  {
1348  return valid_tag(pair.first) &&
1349  std::all_of(pair.second.begin(), pair.second.end(),
1350  [](const auto& c) { return c->validate_wml(); });
1351  }) &&
1352  std::all_of(values_.begin(), values_.end(), [](const auto& pair)
1353  {
1354  return valid_attribute(pair.first);
1355  });
1356 }
1357 
1358 bool operator==(const config& a, const config& b)
1359 {
1360  if(a.values_ != b.values_) {
1361  return false;
1362  }
1363 
1364  config::const_all_children_itors x = a.all_children_range(), y = b.all_children_range();
1365  for(; !x.empty() && !y.empty(); x.pop_front(), y.pop_front()) {
1366  if(x.front().key != y.front().key || x.front().cfg != y.front().cfg) {
1367  return false;
1368  }
1369  }
1370 
1371  return x.empty() && y.empty();
1372 }
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:860
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:1190
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:1344
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:870
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:806
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::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:865
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:1119
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:832
void inherit_from(const config &c)
Merge config 'c' into this config, preserving this config's values.
Definition: config.cpp:1169
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:880
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:1022
~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:875
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:906
config()
Definition: config.cpp:76
std::string debug() const
Definition: config.cpp:1236
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:1181
std::size_t find_total_first_of(config_key_type key, std::size_t start=0)
Definition: config.cpp:498
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:1083
void swap(config &cfg)
Definition: config.cpp:1332
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:839
bool empty() const
Definition: config.cpp:845
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:824
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:1279
friend bool operator==(const config &a, const config &b)
Definition: config.cpp:1358
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:1243
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
std::size_t i
Definition: function.cpp:1029
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
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 formatting tag.
Definition: markup.hpp:50
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
constexpr auto filter
Definition: ranges.hpp:38
bool wildcard_string_match(const std::string &str, const std::string &match)
Match using '*' as any number of characters (including none), '+' as one or more characters,...
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
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:850
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