The Battle for Wesnoth  1.19.5+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  if(children_.empty()) {
177  //optimisation
178  children_ = std::move(cfg.children_);
179  ordered_children = std::move(cfg.ordered_children);
180  cfg.clear_all_children();
181  return;
182  }
183  for(const auto [child_key, child_value] : cfg.all_children_view()) {
184  add_child(child_key, std::move(child_value));
185  }
186  cfg.clear_all_children();
187 }
188 
190 {
191  for(const auto& [key, value] : cfg.values_) {
192  values_[key] = value;
193  }
194 }
195 
197 {
198  for(const config& value : cfg.child_range(key)) {
199  add_child(key, value);
200  }
201 }
202 
203 void config::append(const config& cfg)
204 {
205  append_children(cfg);
206  for(const auto& [key, value] : cfg.values_) {
207  values_[key] = value;
208  }
209 }
210 
212 {
213  append_children(std::move(cfg));
214 
215  if(values_.empty()) {
216  //optimisation.
217  values_ = std::move(cfg.values_);
218  }
219  else {
220  for(const auto& [key, value] : cfg.values_) {
221  //TODO: move the attributes as well?
222  values_[key] = value;
223  }
224  }
225  cfg.clear_attributes();
226 }
227 
229 {
230  // DO note this leaves the tags empty in the source config. Not sure if
231  // that should be changed.
232  for(config& value : cfg.child_range(key)) {
233  add_child(key, std::move(value));
234  }
235 
236  cfg.clear_children_impl(key);
237 }
238 
240 {
241  if(child_count(key) < 2) {
242  return;
243  }
244 
245  config merged_children;
246  for(config& cfg : child_range(key)) {
247  merged_children.append(std::move(cfg));
248  }
249 
250  clear_children_impl(key);
251  add_child(key, std::move(merged_children));
252 }
253 
255 {
256  if(child_count(key) < 2) {
257  return;
258  }
259 
260  typedef std::map<std::string, config> config_map;
261  config_map merged_children_map;
262  for(config& cfg : child_range(key)) {
263  merged_children_map[cfg[attribute]].append(std::move(cfg));
264  }
265 
266  clear_children_impl(key);
267  for(const config_map::value_type& i : merged_children_map) {
268  add_child(key, i.second); // TODO: can we use std::move?
269  }
270 }
271 
273 {
274  child_map::iterator i = children_.find(key);
275  static child_list dummy;
276  child_list* p = &dummy;
277  if(i != children_.end()) {
278  p = &i->second;
279  }
280 
281  return child_itors(child_iterator(p->begin()), child_iterator(p->end()));
282 }
283 
285 {
286  child_map::const_iterator i = children_.find(key);
287  static child_list dummy;
288  const child_list* p = &dummy;
289  if(i != children_.end()) {
290  p = &i->second;
291  }
292 
294 }
295 
296 std::size_t config::child_count(config_key_type key) const
297 {
298  child_map::const_iterator i = children_.find(key);
299  if(i != children_.end()) {
300  return i->second.size();
301  }
302 
303  return 0;
304 }
305 
306 std::size_t config::all_children_count() const
307 {
308  return ordered_children.size();
309 }
310 
311 std::size_t config::attribute_count() const
312 {
313  return std::count_if(values_.begin(), values_.end(), [](const attribute& v) { return !v.second.blank(); });
314 }
315 
317 {
318  child_map::const_iterator i = children_.find(key);
319  return i != children_.end() && !i->second.empty();
320 }
321 
322 namespace {
323 template<class Tchildren>
324 auto get_child_impl(Tchildren& children, config_key_type key, int n) -> optional_config_impl<std::remove_reference_t<decltype(**(*children.begin()).second.begin())>>
325 {
326 
327  auto i = children.find(key);
328  if(i == children.end()) {
329  DBG_CF << "The config object has no child named ‘" << key << "’.";
330  return utils::nullopt;
331  }
332 
333  if(n < 0) {
334  n = static_cast<int>(i->second.size()) + n;
335  }
336 
337  try {
338  return *i->second.at(n);
339  } catch(const std::out_of_range&) {
340  DBG_CF << "The config object has only ‘" << i->second.size() << "’ children named ‘" << key
341  << "’; request for the index ‘" << n << "’ cannot be honored.";
342  return utils::nullopt;
343  }
344 }
345 
346 }
347 
348 config& config::mandatory_child(config_key_type key, const std::string& parent)
349 {
350  if(auto res = get_child_impl(children_, key, 0)) {
351  return *res;
352  } else {
353  throw error("Mandatory WML child ‘[" + std::string(key) + "]’ missing in ‘" + parent + "’. Please report this bug.");
354  }
355 }
356 
357 const config& config::mandatory_child(config_key_type key, const std::string& parent) const
358 {
359  if(auto res = get_child_impl(children_, key, 0)) {
360  return *res;
361  } else {
362  throw error("Mandatory WML child ‘[" + std::string(key) + "]’ missing in ‘" + parent + "’. Please report this bug.");
363  }
364 }
365 
367 {
368  if(auto res = get_child_impl(children_, key, n)) {
369  return *res;
370  } else {
371  throw error("Child [" + std::string(key) + "] at index " + std::to_string(n) + " not found");
372  }
373 }
374 
376 {
377  if(auto res = get_child_impl(children_, key, n)) {
378  return *res;
379  } else {
380  throw error("Child [" + std::string(key) + "] at index " + std::to_string(n) + " not found");
381  }
382 }
383 
385 {
386  return get_child_impl(children_, key, n);
387 }
388 
390 {
391  return get_child_impl(children_, key, n);
392 }
393 
395 {
396  static const config empty_cfg;
397  child_map::const_iterator i = children_.find(key);
398  if(i != children_.end() && !i->second.empty()) {
399  return *i->second.front();
400  }
401 
402  return empty_cfg;
403 }
404 
406 {
407  child_map::const_iterator i = children_.find(key);
408  if(i != children_.end() && !i->second.empty()) {
409  return *i->second.front();
410  }
411 
412  return add_child(key);
413 }
414 
415 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
416 {
417  if(auto res = optional_child(old_key)) {
418  const std::string what = formatter() << "[" << in_tag << "][" << old_key << "]";
419  deprecated_message(what, level, "", message);
420  return res;
421  }
422 
423  return utils::nullopt;
424 }
425 
426 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
427 {
428  static child_list dummy;
429  const child_list* p = &dummy;
430 
431  if(auto i = children_.find(old_key); i != children_.end() && !i->second.empty()) {
432  const std::string what = formatter() << "[" << in_tag << "][" << old_key << "]";
433  deprecated_message(what, level, "", message);
434  p = &i->second;
435  }
436 
438 }
439 
441 {
442  auto iter = map_get(children_, key);
443  child_list& v = iter->second;
444  v.emplace_back(new config());
445  ordered_children.emplace_back(iter, v.size() - 1);
446  return *v.back();
447 }
448 
450 {
451  auto iter = map_get(children_, key);
452  child_list& v = iter->second;
453  v.emplace_back(new config(val));
454  ordered_children.emplace_back(iter, v.size() - 1);
455 
456  return *v.back();
457 }
458 
460 {
461  auto iter = map_get(children_, key);
462  child_list& v = iter->second;
463  v.emplace_back(new config(std::move(val)));
464  ordered_children.emplace_back(iter, v.size() - 1);
465 
466  return *v.back();
467 }
468 
470 {
471  auto iter = map_get(children_, key);
472  child_list& v = iter->second;
473  if(index > v.size()) {
474  throw error("illegal index to add child at");
475  }
476 
477  v.emplace(v.begin() + index, new config(val));
478 
479  bool inserted = false;
480 
481  const child_pos value(iter, index);
482 
484  for(; ord != ordered_children.end(); ++ord) {
485  if(ord->pos != value.pos)
486  continue;
487  if(!inserted && ord->index == index) {
488  ord = ordered_children.insert(ord, value);
489  inserted = true;
490  } else if(ord->index >= index) {
491  ord->index++;
492  }
493  }
494 
495  if(!inserted) {
496  ordered_children.push_back(value);
497  }
498 
499  return *v[index];
500 }
501 
503 {
504  assert(start <= ordered_children.size());
505  const size_t npos = static_cast<size_t>(-1);
506 
507  auto pos = std::find_if(ordered_begin() + start, ordered_end(), [&](const config::any_child& can){ return can.key == key; });
508 
509  if(pos == ordered_end()) {
510  return npos;
511  }
512 
513  return static_cast<size_t>(pos - ordered_begin());
514 }
515 
516 config& config::add_child_at_total(config_key_type key, const config &val, std::size_t pos)
517 {
518  assert(pos <= ordered_children.size());
519  if(pos == ordered_children.size()) {
520  //optimisation
521  return config::add_child(key, val);
522  }
523 
524  auto end = ordered_children.end();
525  auto pos_it = ordered_children.begin() + pos;
526  auto next = std::find_if(pos_it, end,[&](const child_pos& p){ return p.pos->first == key; });
527 
528  if(next == end) {
529  config& res = config::add_child(key, val);
530  //rotate the just inserted element to position pos.
531  std::rotate(ordered_children.begin() + pos, ordered_children.end() - 1, ordered_children.end());
532  return res;
533  }
534 
535  auto pl = next->pos;
536  child_list& l = pl->second;
537  const auto index = next->index;
538  config& res = **(l.emplace(l.begin() + index, new config(val)));
539 
540  for(auto ord = next; ord != end; ++ord) {
541  //this changes next->index and all later refernces to that tag.
542  if(ord->pos == pl) {
543  ++ord->index;
544  }
545  }
546 
547  //finally insert our new child in ordered_children.
548  ordered_children.insert(pos_it, { pl, index });
549  return res;
550 }
551 
552 namespace
553 {
554 struct remove_ordered
555 {
556  remove_ordered(const config::child_map::iterator& iter)
557  : iter_(iter)
558  {
559  }
560 
561  bool operator()(const config::child_pos& pos) const
562  {
563  return pos.pos == iter_;
564  }
565 
566 private:
568 };
569 } // end anon namespace
570 
572 {
573  child_map::iterator i = children_.find(key);
574  if(i == children_.end())
575  return;
576 
577  utils::erase_if(ordered_children, remove_ordered{i});
578  children_.erase(i);
579 }
580 
582 {
583  child_map::iterator i_src = src.children_.find(key);
584  if(i_src == src.children_.end()) {
585  return;
586  }
587 
588  utils::erase_if(src.ordered_children, remove_ordered{i_src});
589 
590  auto i_dst = map_get(children_, key);
591  child_list& dst = i_dst->second;
592 
593  const auto before = dst.size();
594  dst.insert(dst.end(), std::make_move_iterator(i_src->second.begin()), std::make_move_iterator(i_src->second.end()));
595  src.children_.erase(i_src);
596  // key might be a reference to i_src->first, so it is no longer usable.
597 
598  for(std::size_t j = before; j < dst.size(); ++j) {
599  ordered_children.emplace_back(i_dst, j);
600  }
601 }
602 
604 {
605  map_erase_key(values_, key);
606 
607  for(std::pair<const std::string, child_list>& p : children_) {
608  for(auto& cfg : p.second) {
609  cfg->recursive_clear_value(key);
610  }
611  }
612 }
613 
615 {
616  /* Find the position with the correct index and decrement all the
617  indices in the ordering that are above this index. */
618  std::size_t found = 0;
619  for(child_pos& p : ordered_children) {
620  if(p.pos != pos) {
621  continue;
622  }
623 
624  if(p.index == index) {
625  found = &p - &ordered_children.front();
626  } else if(p.index > index) {
627  --p.index;
628  }
629  }
630 
631  // Remove from the child map.
632  pos->second.erase(pos->second.begin() + index);
633 
634  // Erase from the ordering and return the next position.
635  return ordered_children.erase(ordered_children.begin() + found);
636 }
637 
639 {
640  return all_children_iterator(remove_child(i.i_->pos, i.i_->index));
641 }
642 
644 {
645  child_map::iterator i = children_.find(key);
646  if(i == children_.end() || index >= i->second.size()) {
647  ERR_CF << "Error: attempting to delete non-existing child: " << key << "[" << index << "]";
648  return;
649  }
650 
651  remove_child(i, index);
652 }
653 
654 void config::remove_children(config_key_type key, std::function<bool(const config&)> p)
655 {
656  child_map::iterator pos = children_.find(key);
657  if(pos == children_.end()) {
658  return;
659  }
660 
661  const auto predicate = [p](const std::unique_ptr<config>& child)
662  {
663  return !p || p(*child);
664  };
665 
666  auto child_it = std::find_if(pos->second.begin(), pos->second.end(), predicate);
667  while(child_it != pos->second.end()) {
668  const auto index = std::distance(pos->second.begin(), child_it);
669  remove_child(pos, index);
670  child_it = std::find_if(pos->second.begin() + index, pos->second.end(), predicate);
671  }
672 }
673 
675 {
676  const attribute_map::const_iterator i = values_.find(key);
677  if(i != values_.end()) {
678  return i->second;
679  }
680 
681  static const attribute_value empty_attribute;
682  return empty_attribute;
683 }
684 
686 {
687  attribute_map::const_iterator i = values_.find(key);
688  return i != values_.end() ? &i->second : nullptr;
689 }
690 
691 const config::attribute_value& config::get_or(const config_key_type key, const config_key_type default_key) const
692 {
693  const config::attribute_value & value = operator[](key);
694  return !value.blank() ? value : operator[](default_key);
695 }
696 
698 {
699  auto res = values_.lower_bound(key);
700 
701  if(res == values_.end() || key != res->first) {
702  res = values_.emplace_hint(res, std::piecewise_construct, std::forward_as_tuple(key), std::tuple<>());
703  }
704 
705  return res->second;
706 }
707 
708 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
709 {
710  if(has_attribute(old_key)) {
711  const std::string what = formatter() << "[" << in_tag << "]" << old_key << "=";
712  const std::string msg = formatter() << "Use " << key << "= instead. " << message;
714  }
715 
716  attribute_map::const_iterator i = values_.find(key);
717  if(i != values_.end()) {
718  return i->second;
719  }
720 
721  i = values_.find(old_key);
722  if(i != values_.end()) {
723  return i->second;
724  }
725 
726  static const attribute_value empty_attribute;
727  return empty_attribute;
728 }
729 
730 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
731 {
732  if(auto i = values_.find(old_key); i != values_.end()) {
733  const std::string what = formatter() << "[" << in_tag << "]" << old_key << "=";
734  deprecated_message(what, level, "", message);
735  return i->second;
736  }
737 
738  static const attribute_value empty_attribute;
739  return empty_attribute;
740 }
741 
743 {
744  assert(this != &cfg);
745  for(const auto& [key, value] : cfg.values_) {
746  if(key.substr(0, 7) == "add_to_") {
747  std::string add_to = key.substr(7);
748  values_[add_to] = values_[add_to].to_double() + value.to_double();
749  } else if(key.substr(0, 10) == "concat_to_") {
750  std::string concat_to = key.substr(10);
751  // TODO: Only use t_string if one or both are actually translatable?
752  // That probably requires using a visitor though.
753  values_[concat_to] = values_[concat_to].t_str() + value.t_str();
754  } else {
755  values_[key] = value;
756  }
757  }
758 }
759 
761 {
763 
764  // Ensure the first element is not blank, as a few places assume this
765  while(range.begin() != range.end() && range.begin()->second.blank()) {
766  range.pop_front();
767  }
768 
769  return range;
770 }
771 
773 {
775 
776  // Ensure the first element is not blank, as a few places assume this
777  while(range.begin() != range.end() && range.begin()->second.blank()) {
778  range.pop_front();
779  }
780 
781  return range;
782 }
783 
784 optional_config config::find_child(config_key_type key, const std::string& name, const std::string& value)
785 {
786  const child_map::iterator i = children_.find(key);
787  if(i == children_.end()) {
788  DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
789 
790 
791  return utils::nullopt;
792  }
793 
794  const child_list::iterator j = std::find_if(i->second.begin(), i->second.end(),
795  [&](const std::unique_ptr<config>& pcfg) {
796  const config& cfg = *pcfg;
797  return cfg[name] == value;
798  }
799  );
800 
801  if(j != i->second.end()) {
802  return **j;
803  }
804 
805  DBG_CF << "Key ‘" << name << "’ value ‘" << value << "’ pair not found as child of key ‘" << key << "’.";
806 
807  return utils::nullopt;
808 }
809 
810 config& config::find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
811 {
812  auto res = find_child(key, name, value);
813  if(res) {
814  return *res;
815  }
816  throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
817 }
818 
819 const config& config::find_mandatory_child(config_key_type key, const std::string &name, const std::string &value) const
820 {
821  auto res = find_child(key, name, value);
822  if(res) {
823  return *res;
824  }
825  throw error("Cannot find child [" + std::string(key) + "] with " + name + "=" + value);
826 }
827 
829 {
830  // No validity check for this function.
831  children_.clear();
832  values_.clear();
833  ordered_children.clear();
834 }
835 
837 {
838  // No validity check for this function.
839  children_.clear();
840  ordered_children.clear();
841 }
842 
844 {
845  // No validity check for this function.
846  values_.clear();
847 }
848 
849 bool config::empty() const
850 {
851  return children_.empty() && values_.empty();
852 }
853 
855 {
856  return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
857 }
858 
860 {
861  return any_child(&i_->pos->first, i_->pos->second[i_->index].get());
862 }
863 
865 {
867 }
868 
870 {
872 }
873 
875 {
877 }
878 
880 {
882 }
883 
885 {
889  );
890 }
891 
893 {
894  return all_children_iterator(ordered_children.begin());
895 }
896 
898 {
900 }
901 
903 {
904  return all_children_itors(
907  );
908 }
909 
911 {
912  config res;
913  get_diff(c, res);
914  return res;
915 }
916 
917 void config::get_diff(const config& c, config& res) const
918 {
919  config* inserts = nullptr;
920 
921  for(const auto& [key, value] : values_) {
922  if(value.blank()) {
923  continue;
924  }
925 
926  const attribute_map::const_iterator j = c.values_.find(key);
927  if(j == c.values_.end() || (value != j->second && !value.blank())) {
928  if(inserts == nullptr) {
929  inserts = &res.add_child("insert");
930  }
931 
932  (*inserts)[key] = value;
933  }
934  }
935 
936  config* deletes = nullptr;
937 
938  for(const auto& [key, value] : c.values_) {
939  if(value.blank()) {
940  continue;
941  }
942 
943  const attribute_map::const_iterator itor = values_.find(key);
944  if(itor == values_.end() || itor->second.blank()) {
945  if(deletes == nullptr) {
946  deletes = &res.add_child("delete");
947  }
948 
949  (*deletes)[key] = "x";
950  }
951  }
952 
953  std::vector<std::string> entities;
954 
955  for(const auto& child : children_) {
956  entities.push_back(child.first);
957  }
958 
959  for(const auto& child : c.children_) {
960  if(children_.count(child.first) == 0) {
961  entities.push_back(child.first);
962  }
963  }
964 
965  for(const std::string& entity : entities) {
966  const child_map::const_iterator itor_a = children_.find(entity);
967  const child_map::const_iterator itor_b = c.children_.find(entity);
968 
969  static const child_list dummy;
970 
971  // Get the two child lists. 'b' has to be modified to look like 'a'.
972  const child_list& a = itor_a != children_.end() ? itor_a->second : dummy;
973  const child_list& b = itor_b != c.children_.end() ? itor_b->second : dummy;
974 
975  std::size_t ndeletes = 0;
976  std::size_t ai = 0, bi = 0;
977  while(ai != a.size() || bi != b.size()) {
978  // If the two elements are the same, nothing needs to be done.
979  if(ai < a.size() && bi < b.size() && *a[ai] == *b[bi]) {
980  ++ai;
981  ++bi;
982  } else {
983  // We have to work out what the most appropriate operation --
984  // delete, insert, or change is the best to get b[bi] looking like a[ai].
985  std::stringstream buf;
986 
987  // If b has more elements than a, then we assume this element
988  // is an element that needs deleting.
989  if(b.size() - bi > a.size() - ai) {
990  config& new_delete = res.add_child("delete_child");
991  buf << bi - ndeletes;
992  new_delete.values_["index"] = buf.str();
993  new_delete.add_child(entity);
994 
995  ++ndeletes;
996  ++bi;
997  }
998 
999  // If b has less elements than a, then we assume this element
1000  // is an element that needs inserting.
1001  else if(b.size() - bi < a.size() - ai) {
1002  config& new_insert = res.add_child("insert_child");
1003  buf << ai;
1004  new_insert.values_["index"] = buf.str();
1005  new_insert.add_child(entity, *a[ai]);
1006 
1007  ++ai;
1008  }
1009 
1010  // Otherwise, they have the same number of elements,
1011  // so try just changing this element to match.
1012  else {
1013  config& new_change = res.add_child("change_child");
1014  buf << bi;
1015  new_change.values_["index"] = buf.str();
1016  new_change.add_child(entity, a[ai]->get_diff(*b[bi]));
1017 
1018  ++ai;
1019  ++bi;
1020  }
1021  }
1022  }
1023  }
1024 }
1025 
1026 void config::apply_diff(const config& diff, bool track /* = false */)
1027 {
1028  if(track) {
1029  values_[diff_track_attribute] = "modified";
1030  }
1031 
1032  if(const auto inserts = diff.optional_child("insert")) {
1033  for(const auto& [key, value] : inserts->attribute_range()) {
1034  values_[key] = value;
1035  }
1036  }
1037 
1038  if(const auto deletes = diff.optional_child("delete")) {
1039  for(const attribute& v : deletes->attribute_range()) {
1040  values_.erase(v.first);
1041  }
1042  }
1043 
1044  for(const config& i : diff.child_range("change_child")) {
1045  const std::size_t index = lexical_cast<std::size_t>(i["index"].str());
1046  for(const auto [key, cfg] : i.all_children_view()) {
1047  if(key.empty()) {
1048  continue;
1049  }
1050 
1051  const child_map::iterator itor = children_.find(key);
1052  if(itor == children_.end() || index >= itor->second.size()) {
1053  throw error("error in diff: could not find element '" + key + "'");
1054  }
1055 
1056  itor->second[index]->apply_diff(cfg, track);
1057  }
1058  }
1059 
1060  for(const config& i : diff.child_range("insert_child")) {
1061  const auto index = lexical_cast<std::size_t>(i["index"].str());
1062  for(const auto [key, cfg] : i.all_children_view()) {
1063  config& inserted = add_child_at(key, cfg, index);
1064  if(track) {
1065  inserted[diff_track_attribute] = "new";
1066  }
1067  }
1068  }
1069 
1070  for(const config& i : diff.child_range("delete_child")) {
1071  const auto index = lexical_cast<std::size_t>(i["index"].str());
1072  for(const auto [key, cfg] : i.all_children_view()) {
1073  if(!track) {
1074  remove_child(key, index);
1075  } else {
1076  const child_map::iterator itor = children_.find(key);
1077  if(itor == children_.end() || index >= itor->second.size()) {
1078  throw error("error in diff: could not find element '" + key + "'");
1079  }
1080 
1081  itor->second[index]->values_[diff_track_attribute] = "deleted";
1082  }
1083  }
1084  }
1085 }
1086 
1088 {
1090  for(const config& i : diff.child_range("delete_child")) {
1091  const auto index = lexical_cast<std::size_t>(i["index"].str());
1092  for(const auto [key, cfg] : i.all_children_view()) {
1093  remove_child(key, index);
1094  }
1095  }
1096 
1097  for(const config& i : diff.child_range("change_child")) {
1098  const std::size_t index = lexical_cast<std::size_t>(i["index"].str());
1099  for(const auto [key, cfg] : i.all_children_view()) {
1100  if(key.empty()) {
1101  continue;
1102  }
1103 
1104  const child_map::iterator itor = children_.find(key);
1105  if(itor == children_.end() || index >= itor->second.size()) {
1106  throw error("error in diff: could not find element '" + key + "'");
1107  }
1108 
1109  itor->second[index]->clear_diff_track(cfg);
1110  }
1111  }
1112 
1113  for(std::pair<const std::string, child_list>& p : children_) {
1114  for(auto& cfg : p.second) {
1115  cfg->remove_attribute(diff_track_attribute);
1116  }
1117  }
1118 }
1119 
1120 /**
1121  * Merge config 'c' into this config, overwriting this config's values.
1122  */
1124 {
1125  std::vector<child_pos> to_remove;
1126  std::map<std::string, unsigned> visitations;
1127 
1128  // Merge attributes first
1130 
1131  // Now merge shared tags
1133  for(i = ordered_children.begin(); i != i_end; ++i) {
1134  const std::string& tag = i->pos->first;
1135  const child_map::const_iterator j = c.children_.find(tag);
1136 
1137  if(j != c.children_.end()) {
1138  unsigned& visits = visitations[tag];
1139 
1140  if(visits < j->second.size()) {
1141  // Get a const config so we do not add attributes.
1142  const config& merge_child = *j->second[visits++];
1143 
1144  if(merge_child["__remove"].to_bool()) {
1145  to_remove.push_back(*i);
1146  } else {
1147  (i->pos->second[i->index])->merge_with(merge_child);
1148  }
1149  }
1150  }
1151  }
1152 
1153  // Now add any unvisited tags
1154  for(const auto& [tag, list] : c.children_) {
1155  unsigned& visits = visitations[tag];
1156  while(visits < list.size()) {
1157  add_child(tag, *list[visits++]);
1158  }
1159  }
1160 
1161  // Remove those marked so
1162  std::map<std::string, std::size_t> removals;
1163  for(const child_pos& pos : to_remove) {
1164  const std::string& tag = pos.pos->first;
1165  auto& removes = removals[tag];
1166  remove_child(tag, pos.index - removes++);
1167  }
1168 }
1169 
1170 /**
1171  * Merge config 'c' into this config, preserving this config's values.
1172  */
1174 {
1175  // Using a scratch config and merge_with() seems to execute about as fast
1176  // as direct coding of this merge.
1177  config scratch(c);
1178  scratch.merge_with(*this);
1179  swap(scratch);
1180 }
1181 
1182 /**
1183  * Merge the attributes of config 'c' into this config, preserving this config's values.
1184  */
1186 {
1187  for(const auto& [key, value] : cfg.values_) {
1188  attribute_value& v2 = values_[key];
1189  if(v2.blank()) {
1190  v2 = value;
1191  }
1192  }
1193 }
1194 bool config::matches(const config& filter) const
1195 {
1196  bool result = true;
1197 
1198  for(const auto& [key, value] : filter.attribute_range()) {
1199  if(key.compare(0, 8, "glob_on_") == 0) {
1200  const attribute_value* v = get(key.substr(8));
1201  if(!v || !utils::wildcard_string_match(v->str(), value.str())) {
1202  result = false;
1203  break;
1204  }
1205  } else {
1206  const attribute_value* v = get(key);
1207  if(!v || *v != value) {
1208  result = false;
1209  break;
1210  }
1211  }
1212  }
1213 
1214  for(const auto [key, cfg] : filter.all_children_view()) {
1215  if(key == "not") {
1216  result = result && !matches(cfg);
1217  continue;
1218  } else if(key == "and") {
1219  result = result && matches(cfg);
1220  continue;
1221  } else if(key == "or") {
1222  result = result || matches(cfg);
1223  continue;
1224  }
1225 
1226  bool found = false;
1227  for(const config& j : child_range(key)) {
1228  if(j.matches(cfg)) {
1229  found = true;
1230  break;
1231  }
1232  }
1233 
1234  result = result && found;
1235  }
1236 
1237  return result;
1238 }
1239 
1240 std::string config::debug() const
1241 {
1242  std::ostringstream outstream;
1243  outstream << *this;
1244  return outstream.str();
1245 }
1246 
1247 std::ostream& operator<<(std::ostream& outstream, const config& cfg)
1248 {
1249  static int i = 0;
1250  i++;
1251 
1252  for(const auto& [key, value] : cfg.attribute_range()) {
1253  if(value.blank()) {
1254  continue;
1255  }
1256 
1257  for(int j = 0; j < i - 1; j++) {
1258  outstream << '\t';
1259  }
1260 
1261  outstream << key << " = " << value << '\n';
1262  }
1263 
1264  for(const auto [key, cfg] : cfg.all_children_view()) {
1265  for(int j = 0; j < i - 1; ++j) {
1266  outstream << '\t';
1267  }
1268 
1269  outstream << "[" << key << "]\n";
1270  outstream << cfg;
1271 
1272  for(int j = 0; j < i - 1; ++j) {
1273  outstream << '\t';
1274  }
1275 
1276  outstream << "[/" << key << "]\n";
1277  }
1278 
1279  i--;
1280  return outstream;
1281 }
1282 
1283 std::string config::hash() const
1284 {
1285  static const unsigned int hash_length = 128;
1286  static const char hash_string[] = "+-,.<>0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1287  char hash_str[hash_length + 1];
1288 
1289  unsigned int i;
1290  for(i = 0; i != hash_length; ++i) {
1291  hash_str[i] = 'a';
1292  }
1293 
1294  hash_str[hash_length] = 0;
1295 
1296  i = 0;
1297  for(const auto& [key, value] : values_) {
1298  if(value.blank()) {
1299  continue;
1300  }
1301 
1302  for(char c : key) {
1303  hash_str[i] ^= c;
1304  if(++i == hash_length) {
1305  i = 0;
1306  }
1307  }
1308 
1309  std::string base_str = value.t_str().base_str();
1310  for(const char c : base_str) {
1311  hash_str[i] ^= c;
1312  if(++i == hash_length) {
1313  i = 0;
1314  }
1315  }
1316  }
1317 
1318  for(const auto [key, cfg] : all_children_view()) {
1319  std::string child_hash = cfg.hash();
1320  for(char c : child_hash) {
1321  hash_str[i] ^= c;
1322  ++i;
1323  if(i == hash_length) {
1324  i = 0;
1325  }
1326  }
1327  }
1328 
1329  for(i = 0; i != hash_length; ++i) {
1330  hash_str[i] = hash_string[static_cast<unsigned>(hash_str[i]) % strlen(hash_string)];
1331  }
1332 
1333  return std::string(hash_str);
1334 }
1335 
1337 {
1338  values_.swap(cfg.values_);
1339  children_.swap(cfg.children_);
1341 }
1342 
1343 void swap(config& lhs, config& rhs)
1344 {
1345  lhs.swap(rhs);
1346 }
1347 
1349 {
1350  return std::all_of(children_.begin(), children_.end(), [](const auto& pair)
1351  {
1352  return valid_tag(pair.first) &&
1353  std::all_of(pair.second.begin(), pair.second.end(),
1354  [](const auto& c) { return c->validate_wml(); });
1355  }) &&
1356  std::all_of(values_.begin(), values_.end(), [](const auto& pair)
1357  {
1358  return valid_attribute(pair.first);
1359  });
1360 }
1361 
1362 bool operator==(const config& a, const config& b)
1363 {
1364  if(a.values_ != b.values_) {
1365  return false;
1366  }
1367 
1368  config::const_all_children_itors x = a.all_children_range(), y = b.all_children_range();
1369  for(; !x.empty() && !y.empty(); x.pop_front(), y.pop_front()) {
1370  if(x.front().key != y.front().key || x.front().cfg != y.front().cfg) {
1371  return false;
1372  }
1373  }
1374 
1375  return x.empty() && y.empty();
1376 }
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:172
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:254
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:708
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:203
const attribute_value & get_or(const config_key_type key, const config_key_type default_key) const
Chooses a value.
Definition: config.cpp:691
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:638
const_all_children_iterator ordered_begin() const
Definition: config.cpp:864
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:777
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:394
std::size_t attribute_count() const
Count the number of non-blank attributes.
Definition: config.cpp:311
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:366
attribute_value & operator[](config_key_type key)
Returns a reference to the attribute with the given key.
Definition: config.cpp:697
void recursive_clear_value(config_key_type key)
Definition: config.cpp:603
bool matches(const config &filter) const
Definition: config.cpp:1194
void remove_child(config_key_type key, std::size_t index)
Definition: config.cpp:643
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:239
const_attr_itors attribute_range() const
Definition: config.cpp:760
std::size_t child_count(config_key_type key) const
Definition: config.cpp:296
attribute_map values_
All the attributes of this node.
Definition: config.hpp:943
bool validate_wml() const
Returns true if this object represents valid WML, i.e.
Definition: config.cpp:1348
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:415
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:810
void merge_attributes(const config &)
Definition: config.cpp:742
const_all_children_iterator ordered_end() const
Definition: config.cpp:874
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:295
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:469
config & find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
Definition: config.cpp:810
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:730
std::vector< std::unique_ptr< config > > child_list
Definition: config.hpp:205
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:784
static bool valid_tag(config_key_type name)
Definition: config.cpp:129
boost::iterator_range< attribute_iterator > attr_itors
Definition: config.hpp:373
const_all_children_iterator ordered_cbegin() const
Definition: config.cpp:869
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
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:1123
void clear_children_impl(config_key_type key)
Definition: config.cpp:571
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:426
void clear_all_children()
Definition: config.cpp:836
void inherit_from(const config &c)
Merge config 'c' into this config, preserving this config's values.
Definition: config.cpp:1173
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:884
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition: config.hpp:839
boost::iterator_range< all_children_iterator > all_children_itors
Definition: config.hpp:776
child_itors child_range(config_key_type key)
Definition: config.cpp:272
void append_attributes(const config &cfg)
Adds attributes from cfg.
Definition: config.cpp:189
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:405
boost::iterator_range< const_attribute_iterator > const_attr_itors
Definition: config.hpp:372
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1026
~config()
Definition: config.cpp:99
std::size_t all_children_count() const
Definition: config.cpp:306
child_map children_
A list of all children of this node.
Definition: config.hpp:946
const_all_children_iterator ordered_cend() const
Definition: config.cpp:879
config & add_child_at_total(config_key_type key, const config &val, std::size_t pos)
Definition: config.cpp:516
void remove_attribute(config_key_type key)
Definition: config.cpp:162
void remove_children(config_key_type key, std::function< bool(const config &)> p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:654
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:910
config()
Definition: config.cpp:76
std::string debug() const
Definition: config.cpp:1240
attribute_map::value_type attribute
Definition: config.hpp:312
void inherit_attributes(const config &c)
Merge the attributes of config 'c' into this config, preserving this config's values.
Definition: config.cpp:1185
std::size_t find_total_first_of(config_key_type key, std::size_t start=0)
Definition: config.cpp:502
std::vector< child_pos > ordered_children
Definition: config.hpp:948
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition: config.cpp:1087
void swap(config &cfg)
Definition: config.cpp:1336
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:228
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:296
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:167
void clear_attributes()
Definition: config.cpp:843
bool empty() const
Definition: config.cpp:849
void splice_children(config &src, config_key_type key)
Moves all the children with tag key from src to this.
Definition: config.cpp:581
void clear()
Definition: config.cpp:828
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:685
std::string hash() const
Definition: config.cpp:1283
friend bool operator==(const config &a, const config &b)
Definition: config.cpp:1362
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:384
config & operator=(const config &)
Definition: config.cpp:104
config & add_child(config_key_type key)
Definition: config.cpp:440
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:1247
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:62
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:1028
static auto & dummy
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(const std::string &tag_name, Args &&... contents)
Definition: markup.hpp:45
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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:100
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:677
reference operator*() const
Definition: config.cpp:854
const child_map::key_type & key
Definition: config.hpp:657
child_map::iterator pos
Definition: config.hpp:648
constexpr point size() const
Definition: rect.hpp:65
mock_char c
mock_party p
static map_location::direction n
#define b