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