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