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