|
| 1 | +/* |
| 2 | +MIT License |
| 3 | +
|
| 4 | +Copyright (c) 2017-2018 Matthias C. M. Troffaes |
| 5 | +
|
| 6 | +Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | +of this software and associated documentation files (the "Software"), to deal |
| 8 | +in the Software without restriction, including without limitation the rights |
| 9 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | +copies of the Software, and to permit persons to whom the Software is |
| 11 | +furnished to do so, subject to the following conditions: |
| 12 | +
|
| 13 | +The above copyright notice and this permission notice shall be included in all |
| 14 | +copies or substantial portions of the Software. |
| 15 | +
|
| 16 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 | +SOFTWARE. |
| 23 | +*/ |
| 24 | + |
| 25 | +#pragma once |
| 26 | + |
| 27 | +#include <cstring> |
| 28 | +#include <string> |
| 29 | +#include <iostream> |
| 30 | +#include <list> |
| 31 | +#include <map> |
| 32 | +#include <algorithm> |
| 33 | +#include <functional> |
| 34 | +#include <cctype> |
| 35 | +#include <sstream> |
| 36 | + |
| 37 | +namespace inipp { |
| 38 | + |
| 39 | +namespace detail { |
| 40 | + |
| 41 | +// trim functions based on http://stackoverflow.com/a/217605 |
| 42 | + |
| 43 | +template <class CharT> |
| 44 | +inline void ltrim(std::basic_string<CharT> & s) { |
| 45 | + s.erase(s.begin(), |
| 46 | + std::find_if(s.begin(), s.end(), |
| 47 | + [](int ch) { return !std::isspace(ch); })); |
| 48 | +} |
| 49 | + |
| 50 | +template <class CharT> |
| 51 | +inline void rtrim(std::basic_string<CharT> & s) { |
| 52 | + s.erase(std::find_if(s.rbegin(), s.rend(), |
| 53 | + [](int ch) { return !std::isspace(ch); }).base(), |
| 54 | + s.end()); |
| 55 | +} |
| 56 | + |
| 57 | +// string replacement function based on http://stackoverflow.com/a/3418285 |
| 58 | + |
| 59 | +template <class CharT> |
| 60 | +inline bool replace(std::basic_string<CharT> & str, const std::basic_string<CharT> & from, const std::basic_string<CharT> & to) { |
| 61 | + auto changed = false; |
| 62 | + size_t start_pos = 0; |
| 63 | + while ((start_pos = str.find(from, start_pos)) != std::basic_string<CharT>::npos) { |
| 64 | + str.replace(start_pos, from.length(), to); |
| 65 | + start_pos += to.length(); |
| 66 | + changed = true; |
| 67 | + } |
| 68 | + return changed; |
| 69 | +} |
| 70 | + |
| 71 | +} // namespace detail |
| 72 | + |
| 73 | +template <typename CharT, typename T> |
| 74 | +inline bool extract(const std::basic_string<CharT> & value, T & dst) { |
| 75 | + CharT c; |
| 76 | + std::basic_istringstream<CharT> is{ value }; |
| 77 | + T result; |
| 78 | + if ((is >> std::boolalpha >> result) && !(is >> c)) { |
| 79 | + dst = result; |
| 80 | + return true; |
| 81 | + } |
| 82 | + else { |
| 83 | + return false; |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +template <typename CharT> |
| 88 | +inline bool extract(const std::basic_string<CharT> & value, std::basic_string<CharT> & dst) { |
| 89 | + dst = value; |
| 90 | + return true; |
| 91 | +} |
| 92 | + |
| 93 | +template<class CharT> |
| 94 | +class Ini |
| 95 | +{ |
| 96 | +public: |
| 97 | + typedef std::basic_string<CharT> String; |
| 98 | + typedef std::map<String, String> Section; |
| 99 | + typedef std::map<String, Section> Sections; |
| 100 | + |
| 101 | + Sections sections; |
| 102 | + std::list<String> errors; |
| 103 | + |
| 104 | + static const CharT char_section_start = (CharT)'['; |
| 105 | + static const CharT char_section_end = (CharT)']'; |
| 106 | + static const CharT char_assign = (CharT)'='; |
| 107 | + static const CharT char_comment = (CharT)';'; |
| 108 | + static const CharT char_interpol = (CharT)'$'; |
| 109 | + static const CharT char_interpol_start = (CharT)'{'; |
| 110 | + static const CharT char_interpol_sep = (CharT)':'; |
| 111 | + static const CharT char_interpol_end = (CharT)'}'; |
| 112 | + |
| 113 | + static const int max_interpolation_depth = 10; |
| 114 | + |
| 115 | + void generate(std::basic_ostream<CharT> & os) const { |
| 116 | + for (auto const & sec : sections) { |
| 117 | + os << char_section_start << sec.first << char_section_end << std::endl; |
| 118 | + for (auto const & val : sec.second) { |
| 119 | + os << val.first << char_assign << val.second << std::endl; |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + void parse(std::basic_istream<CharT> & is) { |
| 125 | + String line; |
| 126 | + String section; |
| 127 | + while (std::getline(is, line)) { |
| 128 | + detail::ltrim(line); |
| 129 | + detail::rtrim(line); |
| 130 | + const auto length = line.length(); |
| 131 | + if (length > 0) { |
| 132 | + const auto pos = line.find_first_of(char_assign); |
| 133 | + const auto & front = line.front(); |
| 134 | + if (front == char_comment) { |
| 135 | + continue; |
| 136 | + } |
| 137 | + else if (front == char_section_start) { |
| 138 | + if (line.back() == char_section_end) |
| 139 | + section = line.substr(1, length - 2); |
| 140 | + else |
| 141 | + errors.push_back(line); |
| 142 | + } |
| 143 | + else if (pos != 0 && pos != String::npos) { |
| 144 | + String variable(line.substr(0, pos)); |
| 145 | + String value(line.substr(pos + 1, length)); |
| 146 | + detail::rtrim(variable); |
| 147 | + detail::ltrim(value); |
| 148 | + auto & sec = sections[section]; |
| 149 | + if (sec.find(variable) == sec.end()) |
| 150 | + sec.insert(std::make_pair(variable, value)); |
| 151 | + else |
| 152 | + errors.push_back(line); |
| 153 | + } |
| 154 | + else { |
| 155 | + errors.push_back(line); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + void interpolate() { |
| 162 | + int global_iteration = 0; |
| 163 | + auto changed = false; |
| 164 | + // replace each "${variable}" by "${section:variable}" |
| 165 | + for (auto & sec : sections) |
| 166 | + replace_symbols(local_symbols(sec.first, sec.second), sec.second); |
| 167 | + // replace each "${section:variable}" by its value |
| 168 | + do { |
| 169 | + changed = false; |
| 170 | + const auto syms = global_symbols(); |
| 171 | + for (auto & sec : sections) |
| 172 | + changed |= replace_symbols(syms, sec.second); |
| 173 | + } while (changed && (max_interpolation_depth > global_iteration++)); |
| 174 | + } |
| 175 | + |
| 176 | + void default_section(const Section & sec) { |
| 177 | + for (auto & sec2 : sections) |
| 178 | + for (const auto & val : sec) |
| 179 | + sec2.second.insert(val); |
| 180 | + } |
| 181 | + |
| 182 | + void clear() { |
| 183 | + sections.clear(); |
| 184 | + errors.clear(); |
| 185 | + } |
| 186 | + |
| 187 | +private: |
| 188 | + typedef std::list<std::pair<String, String> > Symbols; |
| 189 | + |
| 190 | + auto local_symbol(const String & name) const { |
| 191 | + return char_interpol + (char_interpol_start + name + char_interpol_end); |
| 192 | + } |
| 193 | + |
| 194 | + auto global_symbol(const String & sec_name, const String & name) const { |
| 195 | + return local_symbol(sec_name + char_interpol_sep + name); |
| 196 | + } |
| 197 | + |
| 198 | + auto local_symbols(const String & sec_name, const Section & sec) const { |
| 199 | + Symbols result; |
| 200 | + for (const auto & val : sec) |
| 201 | + result.push_back(std::make_pair(local_symbol(val.first), global_symbol(sec_name, val.first))); |
| 202 | + return result; |
| 203 | + } |
| 204 | + |
| 205 | + auto global_symbols() const { |
| 206 | + Symbols result; |
| 207 | + for (const auto & sec : sections) |
| 208 | + for (const auto & val : sec.second) |
| 209 | + result.push_back( |
| 210 | + std::make_pair(global_symbol(sec.first, val.first), val.second)); |
| 211 | + return result; |
| 212 | + } |
| 213 | + |
| 214 | + bool replace_symbols(const Symbols & syms, Section & sec) const { |
| 215 | + auto changed = false; |
| 216 | + for (auto & sym : syms) |
| 217 | + for (auto & val : sec) |
| 218 | + changed |= detail::replace(val.second, sym.first, sym.second); |
| 219 | + return changed; |
| 220 | + } |
| 221 | +}; |
| 222 | + |
| 223 | +} // namespace inipp |
0 commit comments