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