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