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