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