101
+ − 1 /*
+ − 2 * The MIT License (MIT)
+ − 3 * Copyright (c) 2018 Danijel Durakovic
+ − 4 *
+ − 5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ − 6 * this software and associated documentation files (the "Software"), to deal in
+ − 7 * the Software without restriction, including without limitation the rights to
+ − 8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ − 9 * of the Software, and to permit persons to whom the Software is furnished to do
+ − 10 * so, subject to the following conditions:
+ − 11 *
+ − 12 * The above copyright notice and this permission notice shall be included in all
+ − 13 * copies or substantial portions of the Software.
+ − 14 *
+ − 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ − 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ − 17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ − 18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ − 19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ − 20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ − 21 *
+ − 22 */
+ − 23
+ − 24 ///////////////////////////////////////////////////////////////////////////////
+ − 25 //
+ − 26 // /mINI/ v0.9.14
+ − 27 // An INI file reader and writer for the modern age.
+ − 28 //
+ − 29 ///////////////////////////////////////////////////////////////////////////////
+ − 30 //
+ − 31 // A tiny utility library for manipulating INI files with a straightforward
+ − 32 // API and a minimal footprint. It conforms to the (somewhat) standard INI
+ − 33 // format - sections and keys are case insensitive and all leading and
+ − 34 // trailing whitespace is ignored. Comments are lines that begin with a
+ − 35 // semicolon. Trailing comments are allowed on section lines.
+ − 36 //
+ − 37 // Files are read on demand, upon which data is kept in memory and the file
+ − 38 // is closed. This utility supports lazy writing, which only writes changes
+ − 39 // and updates to a file and preserves custom formatting and comments. A lazy
+ − 40 // write invoked by a write() call will read the output file, find what
+ − 41 // changes have been made and update the file accordingly. If you only need to
+ − 42 // generate files, use generate() instead. Section and key order is preserved
+ − 43 // on read, write and insert.
+ − 44 //
+ − 45 ///////////////////////////////////////////////////////////////////////////////
+ − 46 //
+ − 47 // /* BASIC USAGE EXAMPLE: */
+ − 48 //
+ − 49 // /* read from file */
+ − 50 // mINI::INIFile file("myfile.ini");
+ − 51 // mINI::INIStructure ini;
+ − 52 // file.read(ini);
+ − 53 //
+ − 54 // /* read value; gets a reference to actual value in the structure.
+ − 55 // if key or section don't exist, a new empty value will be created */
+ − 56 // std::string& value = ini["section"]["key"];
+ − 57 //
+ − 58 // /* read value safely; gets a copy of value in the structure.
+ − 59 // does not alter the structure */
+ − 60 // std::string value = ini.get("section").get("key");
+ − 61 //
+ − 62 // /* set or update values */
+ − 63 // ini["section"]["key"] = "value";
+ − 64 //
+ − 65 // /* set multiple values */
+ − 66 // ini["section2"].set({
+ − 67 // {"key1", "value1"},
+ − 68 // {"key2", "value2"}
+ − 69 // });
+ − 70 //
+ − 71 // /* write updates back to file, preserving comments and formatting */
+ − 72 // file.write(ini);
+ − 73 //
+ − 74 // /* or generate a file (overwrites the original) */
+ − 75 // file.generate(ini);
+ − 76 //
+ − 77 ///////////////////////////////////////////////////////////////////////////////
+ − 78 //
+ − 79 // Long live the INI file!!!
+ − 80 //
+ − 81 ///////////////////////////////////////////////////////////////////////////////
+ − 82
+ − 83 #ifndef MINI_INI_H_
+ − 84 #define MINI_INI_H_
+ − 85
+ − 86 #include <string>
+ − 87 #include <sstream>
+ − 88 #include <algorithm>
+ − 89 #include <utility>
+ − 90 #include <unordered_map>
+ − 91 #include <vector>
+ − 92 #include <memory>
+ − 93 #include <fstream>
+ − 94 #include <sys/stat.h>
+ − 95 #include <cctype>
+ − 96
+ − 97 namespace mINI
+ − 98 {
+ − 99 namespace INIStringUtil
+ − 100 {
+ − 101 const char* const whitespaceDelimiters = " \t\n\r\f\v";
+ − 102 inline void trim(std::string& str)
+ − 103 {
+ − 104 str.erase(str.find_last_not_of(whitespaceDelimiters) + 1);
+ − 105 str.erase(0, str.find_first_not_of(whitespaceDelimiters));
+ − 106 }
+ − 107 #ifndef MINI_CASE_SENSITIVE
+ − 108 inline void toLower(std::string& str)
+ − 109 {
+ − 110 std::transform(str.begin(), str.end(), str.begin(), [](const char c) {
+ − 111 return static_cast<char>(std::tolower(c));
+ − 112 });
+ − 113 }
+ − 114 #endif
+ − 115 inline void replace(std::string& str, std::string const& a, std::string const& b)
+ − 116 {
+ − 117 if (!a.empty())
+ − 118 {
+ − 119 std::size_t pos = 0;
+ − 120 while ((pos = str.find(a, pos)) != std::string::npos)
+ − 121 {
+ − 122 str.replace(pos, a.size(), b);
+ − 123 pos += b.size();
+ − 124 }
+ − 125 }
+ − 126 }
+ − 127 #ifdef _WIN32
+ − 128 const char* const endl = "\r\n";
+ − 129 #else
+ − 130 const char* const endl = "\n";
+ − 131 #endif
+ − 132 }
+ − 133
+ − 134 template<typename T>
+ − 135 class INIMap
+ − 136 {
+ − 137 private:
+ − 138 using T_DataIndexMap = std::unordered_map<std::string, std::size_t>;
+ − 139 using T_DataItem = std::pair<std::string, T>;
+ − 140 using T_DataContainer = std::vector<T_DataItem>;
+ − 141 using T_MultiArgs = typename std::vector<std::pair<std::string, T>>;
+ − 142
+ − 143 T_DataIndexMap dataIndexMap;
+ − 144 T_DataContainer data;
+ − 145
+ − 146 inline std::size_t setEmpty(std::string& key)
+ − 147 {
+ − 148 std::size_t index = data.size();
+ − 149 dataIndexMap[key] = index;
+ − 150 data.emplace_back(key, T());
+ − 151 return index;
+ − 152 }
+ − 153
+ − 154 public:
+ − 155 using const_iterator = typename T_DataContainer::const_iterator;
+ − 156
+ − 157 INIMap() { }
+ − 158
+ − 159 INIMap(INIMap const& other)
+ − 160 {
+ − 161 std::size_t data_size = other.data.size();
+ − 162 for (std::size_t i = 0; i < data_size; ++i)
+ − 163 {
+ − 164 auto const& key = other.data[i].first;
+ − 165 auto const& obj = other.data[i].second;
+ − 166 data.emplace_back(key, obj);
+ − 167 }
+ − 168 dataIndexMap = T_DataIndexMap(other.dataIndexMap);
+ − 169 }
+ − 170
+ − 171 T& operator[](std::string key)
+ − 172 {
+ − 173 INIStringUtil::trim(key);
+ − 174 #ifndef MINI_CASE_SENSITIVE
+ − 175 INIStringUtil::toLower(key);
+ − 176 #endif
+ − 177 auto it = dataIndexMap.find(key);
+ − 178 bool hasIt = (it != dataIndexMap.end());
+ − 179 std::size_t index = (hasIt) ? it->second : setEmpty(key);
+ − 180 return data[index].second;
+ − 181 }
+ − 182 T get(std::string key) const
+ − 183 {
+ − 184 INIStringUtil::trim(key);
+ − 185 #ifndef MINI_CASE_SENSITIVE
+ − 186 INIStringUtil::toLower(key);
+ − 187 #endif
+ − 188 auto it = dataIndexMap.find(key);
+ − 189 if (it == dataIndexMap.end())
+ − 190 {
+ − 191 return T();
+ − 192 }
+ − 193 return T(data[it->second].second);
+ − 194 }
+ − 195 bool has(std::string key) const
+ − 196 {
+ − 197 INIStringUtil::trim(key);
+ − 198 #ifndef MINI_CASE_SENSITIVE
+ − 199 INIStringUtil::toLower(key);
+ − 200 #endif
+ − 201 return (dataIndexMap.count(key) == 1);
+ − 202 }
+ − 203 void set(std::string key, T obj)
+ − 204 {
+ − 205 INIStringUtil::trim(key);
+ − 206 #ifndef MINI_CASE_SENSITIVE
+ − 207 INIStringUtil::toLower(key);
+ − 208 #endif
+ − 209 auto it = dataIndexMap.find(key);
+ − 210 if (it != dataIndexMap.end())
+ − 211 {
+ − 212 data[it->second].second = obj;
+ − 213 }
+ − 214 else
+ − 215 {
+ − 216 dataIndexMap[key] = data.size();
+ − 217 data.emplace_back(key, obj);
+ − 218 }
+ − 219 }
+ − 220 void set(T_MultiArgs const& multiArgs)
+ − 221 {
+ − 222 for (auto const& it : multiArgs)
+ − 223 {
+ − 224 auto const& key = it.first;
+ − 225 auto const& obj = it.second;
+ − 226 set(key, obj);
+ − 227 }
+ − 228 }
+ − 229 bool remove(std::string key)
+ − 230 {
+ − 231 INIStringUtil::trim(key);
+ − 232 #ifndef MINI_CASE_SENSITIVE
+ − 233 INIStringUtil::toLower(key);
+ − 234 #endif
+ − 235 auto it = dataIndexMap.find(key);
+ − 236 if (it != dataIndexMap.end())
+ − 237 {
+ − 238 std::size_t index = it->second;
+ − 239 data.erase(data.begin() + index);
+ − 240 dataIndexMap.erase(it);
+ − 241 for (auto& it2 : dataIndexMap)
+ − 242 {
+ − 243 auto& vi = it2.second;
+ − 244 if (vi > index)
+ − 245 {
+ − 246 vi--;
+ − 247 }
+ − 248 }
+ − 249 return true;
+ − 250 }
+ − 251 return false;
+ − 252 }
+ − 253 void clear()
+ − 254 {
+ − 255 data.clear();
+ − 256 dataIndexMap.clear();
+ − 257 }
+ − 258 std::size_t size() const
+ − 259 {
+ − 260 return data.size();
+ − 261 }
+ − 262 const_iterator begin() const { return data.begin(); }
+ − 263 const_iterator end() const { return data.end(); }
+ − 264 };
+ − 265
+ − 266 using INIStructure = INIMap<INIMap<std::string>>;
+ − 267
+ − 268 namespace INIParser
+ − 269 {
+ − 270 using T_ParseValues = std::pair<std::string, std::string>;
+ − 271
+ − 272 enum class PDataType : char
+ − 273 {
+ − 274 PDATA_NONE,
+ − 275 PDATA_COMMENT,
+ − 276 PDATA_SECTION,
+ − 277 PDATA_KEYVALUE,
+ − 278 PDATA_UNKNOWN
+ − 279 };
+ − 280
+ − 281 inline PDataType parseLine(std::string line, T_ParseValues& parseData)
+ − 282 {
+ − 283 parseData.first.clear();
+ − 284 parseData.second.clear();
+ − 285 INIStringUtil::trim(line);
+ − 286 if (line.empty())
+ − 287 {
+ − 288 return PDataType::PDATA_NONE;
+ − 289 }
+ − 290 char firstCharacter = line[0];
+ − 291 if (firstCharacter == ';')
+ − 292 {
+ − 293 return PDataType::PDATA_COMMENT;
+ − 294 }
+ − 295 if (firstCharacter == '[')
+ − 296 {
+ − 297 auto commentAt = line.find_first_of(';');
+ − 298 if (commentAt != std::string::npos)
+ − 299 {
+ − 300 line = line.substr(0, commentAt);
+ − 301 }
+ − 302 auto closingBracketAt = line.find_last_of(']');
+ − 303 if (closingBracketAt != std::string::npos)
+ − 304 {
+ − 305 auto section = line.substr(1, closingBracketAt - 1);
+ − 306 INIStringUtil::trim(section);
+ − 307 parseData.first = section;
+ − 308 return PDataType::PDATA_SECTION;
+ − 309 }
+ − 310 }
+ − 311 auto lineNorm = line;
+ − 312 INIStringUtil::replace(lineNorm, "\\=", " ");
+ − 313 auto equalsAt = lineNorm.find_first_of('=');
+ − 314 if (equalsAt != std::string::npos)
+ − 315 {
+ − 316 auto key = line.substr(0, equalsAt);
+ − 317 INIStringUtil::trim(key);
+ − 318 INIStringUtil::replace(key, "\\=", "=");
+ − 319 auto value = line.substr(equalsAt + 1);
+ − 320 INIStringUtil::trim(value);
+ − 321 parseData.first = key;
+ − 322 parseData.second = value;
+ − 323 return PDataType::PDATA_KEYVALUE;
+ − 324 }
+ − 325 return PDataType::PDATA_UNKNOWN;
+ − 326 }
+ − 327 }
+ − 328
+ − 329 class INIReader
+ − 330 {
+ − 331 public:
+ − 332 using T_LineData = std::vector<std::string>;
+ − 333 using T_LineDataPtr = std::shared_ptr<T_LineData>;
+ − 334
+ − 335 bool isBOM = false;
+ − 336
+ − 337 private:
+ − 338 std::ifstream fileReadStream;
+ − 339 T_LineDataPtr lineData;
+ − 340
+ − 341 T_LineData readFile()
+ − 342 {
+ − 343 fileReadStream.seekg(0, std::ios::end);
+ − 344 const std::size_t fileSize = static_cast<std::size_t>(fileReadStream.tellg());
+ − 345 fileReadStream.seekg(0, std::ios::beg);
+ − 346 if (fileSize >= 3) {
+ − 347 const char header[3] = {
+ − 348 static_cast<char>(fileReadStream.get()),
+ − 349 static_cast<char>(fileReadStream.get()),
+ − 350 static_cast<char>(fileReadStream.get())
+ − 351 };
+ − 352 isBOM = (
+ − 353 header[0] == static_cast<char>(0xEF) &&
+ − 354 header[1] == static_cast<char>(0xBB) &&
+ − 355 header[2] == static_cast<char>(0xBF)
+ − 356 );
+ − 357 }
+ − 358 else {
+ − 359 isBOM = false;
+ − 360 }
+ − 361 std::string fileContents;
+ − 362 fileContents.resize(fileSize);
+ − 363 fileReadStream.seekg(isBOM ? 3 : 0, std::ios::beg);
+ − 364 fileReadStream.read(&fileContents[0], fileSize);
+ − 365 fileReadStream.close();
+ − 366 T_LineData output;
+ − 367 if (fileSize == 0)
+ − 368 {
+ − 369 return output;
+ − 370 }
+ − 371 std::string buffer;
+ − 372 buffer.reserve(50);
+ − 373 for (std::size_t i = 0; i < fileSize; ++i)
+ − 374 {
+ − 375 char& c = fileContents[i];
+ − 376 if (c == '\n')
+ − 377 {
+ − 378 output.emplace_back(buffer);
+ − 379 buffer.clear();
+ − 380 continue;
+ − 381 }
+ − 382 if (c != '\0' && c != '\r')
+ − 383 {
+ − 384 buffer += c;
+ − 385 }
+ − 386 }
+ − 387 output.emplace_back(buffer);
+ − 388 return output;
+ − 389 }
+ − 390
+ − 391 public:
+ − 392 INIReader(std::string const& filename, bool keepLineData = false)
+ − 393 {
+ − 394 fileReadStream.open(filename, std::ios::in | std::ios::binary);
+ − 395 if (keepLineData)
+ − 396 {
+ − 397 lineData = std::make_shared<T_LineData>();
+ − 398 }
+ − 399 }
+ − 400 ~INIReader() { }
+ − 401
+ − 402 bool operator>>(INIStructure& data)
+ − 403 {
+ − 404 if (!fileReadStream.is_open())
+ − 405 {
+ − 406 return false;
+ − 407 }
+ − 408 T_LineData fileLines = readFile();
+ − 409 std::string section;
+ − 410 bool inSection = false;
+ − 411 INIParser::T_ParseValues parseData;
+ − 412 for (auto const& line : fileLines)
+ − 413 {
+ − 414 auto parseResult = INIParser::parseLine(line, parseData);
+ − 415 if (parseResult == INIParser::PDataType::PDATA_SECTION)
+ − 416 {
+ − 417 inSection = true;
+ − 418 data[section = parseData.first];
+ − 419 }
+ − 420 else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE)
+ − 421 {
+ − 422 auto const& key = parseData.first;
+ − 423 auto const& value = parseData.second;
+ − 424 data[section][key] = value;
+ − 425 }
+ − 426 if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN)
+ − 427 {
+ − 428 if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection)
+ − 429 {
+ − 430 continue;
+ − 431 }
+ − 432 lineData->emplace_back(line);
+ − 433 }
+ − 434 }
+ − 435 return true;
+ − 436 }
+ − 437 T_LineDataPtr getLines()
+ − 438 {
+ − 439 return lineData;
+ − 440 }
+ − 441 };
+ − 442
+ − 443 class INIGenerator
+ − 444 {
+ − 445 private:
+ − 446 std::ofstream fileWriteStream;
+ − 447
+ − 448 public:
+ − 449 bool prettyPrint = false;
+ − 450
+ − 451 INIGenerator(std::string const& filename)
+ − 452 {
+ − 453 fileWriteStream.open(filename, std::ios::out | std::ios::binary);
+ − 454 }
+ − 455 ~INIGenerator() { }
+ − 456
+ − 457 bool operator<<(INIStructure const& data)
+ − 458 {
+ − 459 if (!fileWriteStream.is_open())
+ − 460 {
+ − 461 return false;
+ − 462 }
+ − 463 if (!data.size())
+ − 464 {
+ − 465 return true;
+ − 466 }
+ − 467 auto it = data.begin();
+ − 468 for (;;)
+ − 469 {
+ − 470 auto const& section = it->first;
+ − 471 auto const& collection = it->second;
+ − 472 fileWriteStream
+ − 473 << "["
+ − 474 << section
+ − 475 << "]";
+ − 476 if (collection.size())
+ − 477 {
+ − 478 fileWriteStream << INIStringUtil::endl;
+ − 479 auto it2 = collection.begin();
+ − 480 for (;;)
+ − 481 {
+ − 482 auto key = it2->first;
+ − 483 INIStringUtil::replace(key, "=", "\\=");
+ − 484 auto value = it2->second;
+ − 485 INIStringUtil::trim(value);
+ − 486 fileWriteStream
+ − 487 << key
+ − 488 << ((prettyPrint) ? " = " : "=")
+ − 489 << value;
+ − 490 if (++it2 == collection.end())
+ − 491 {
+ − 492 break;
+ − 493 }
+ − 494 fileWriteStream << INIStringUtil::endl;
+ − 495 }
+ − 496 }
+ − 497 if (++it == data.end())
+ − 498 {
+ − 499 break;
+ − 500 }
+ − 501 fileWriteStream << INIStringUtil::endl;
+ − 502 if (prettyPrint)
+ − 503 {
+ − 504 fileWriteStream << INIStringUtil::endl;
+ − 505 }
+ − 506 }
+ − 507 return true;
+ − 508 }
+ − 509 };
+ − 510
+ − 511 class INIWriter
+ − 512 {
+ − 513 private:
+ − 514 using T_LineData = std::vector<std::string>;
+ − 515 using T_LineDataPtr = std::shared_ptr<T_LineData>;
+ − 516
+ − 517 std::string filename;
+ − 518
+ − 519 T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original)
+ − 520 {
+ − 521 T_LineData output;
+ − 522 INIParser::T_ParseValues parseData;
+ − 523 std::string sectionCurrent;
+ − 524 bool parsingSection = false;
+ − 525 bool continueToNextSection = false;
+ − 526 bool discardNextEmpty = false;
+ − 527 bool writeNewKeys = false;
+ − 528 std::size_t lastKeyLine = 0;
+ − 529 for (auto line = lineData->begin(); line != lineData->end(); ++line)
+ − 530 {
+ − 531 if (!writeNewKeys)
+ − 532 {
+ − 533 auto parseResult = INIParser::parseLine(*line, parseData);
+ − 534 if (parseResult == INIParser::PDataType::PDATA_SECTION)
+ − 535 {
+ − 536 if (parsingSection)
+ − 537 {
+ − 538 writeNewKeys = true;
+ − 539 parsingSection = false;
+ − 540 --line;
+ − 541 continue;
+ − 542 }
+ − 543 sectionCurrent = parseData.first;
+ − 544 if (data.has(sectionCurrent))
+ − 545 {
+ − 546 parsingSection = true;
+ − 547 continueToNextSection = false;
+ − 548 discardNextEmpty = false;
+ − 549 output.emplace_back(*line);
+ − 550 lastKeyLine = output.size();
+ − 551 }
+ − 552 else
+ − 553 {
+ − 554 continueToNextSection = true;
+ − 555 discardNextEmpty = true;
+ − 556 continue;
+ − 557 }
+ − 558 }
+ − 559 else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE)
+ − 560 {
+ − 561 if (continueToNextSection)
+ − 562 {
+ − 563 continue;
+ − 564 }
+ − 565 if (data.has(sectionCurrent))
+ − 566 {
+ − 567 auto& collection = data[sectionCurrent];
+ − 568 auto const& key = parseData.first;
+ − 569 auto const& value = parseData.second;
+ − 570 if (collection.has(key))
+ − 571 {
+ − 572 auto outputValue = collection[key];
+ − 573 if (value == outputValue)
+ − 574 {
+ − 575 output.emplace_back(*line);
+ − 576 }
+ − 577 else
+ − 578 {
+ − 579 INIStringUtil::trim(outputValue);
+ − 580 auto lineNorm = *line;
+ − 581 INIStringUtil::replace(lineNorm, "\\=", " ");
+ − 582 auto equalsAt = lineNorm.find_first_of('=');
+ − 583 auto valueAt = lineNorm.find_first_not_of(
+ − 584 INIStringUtil::whitespaceDelimiters,
+ − 585 equalsAt + 1
+ − 586 );
+ − 587 std::string outputLine = line->substr(0, valueAt);
+ − 588 if (prettyPrint && equalsAt + 1 == valueAt)
+ − 589 {
+ − 590 outputLine += " ";
+ − 591 }
+ − 592 outputLine += outputValue;
+ − 593 output.emplace_back(outputLine);
+ − 594 }
+ − 595 lastKeyLine = output.size();
+ − 596 }
+ − 597 }
+ − 598 }
+ − 599 else
+ − 600 {
+ − 601 if (discardNextEmpty && line->empty())
+ − 602 {
+ − 603 discardNextEmpty = false;
+ − 604 }
+ − 605 else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN)
+ − 606 {
+ − 607 output.emplace_back(*line);
+ − 608 }
+ − 609 }
+ − 610 }
+ − 611 if (writeNewKeys || std::next(line) == lineData->end())
+ − 612 {
+ − 613 T_LineData linesToAdd;
+ − 614 if (data.has(sectionCurrent) && original.has(sectionCurrent))
+ − 615 {
+ − 616 auto const& collection = data[sectionCurrent];
+ − 617 auto const& collectionOriginal = original[sectionCurrent];
+ − 618 for (auto const& it : collection)
+ − 619 {
+ − 620 auto key = it.first;
+ − 621 if (collectionOriginal.has(key))
+ − 622 {
+ − 623 continue;
+ − 624 }
+ − 625 auto value = it.second;
+ − 626 INIStringUtil::replace(key, "=", "\\=");
+ − 627 INIStringUtil::trim(value);
+ − 628 linesToAdd.emplace_back(
+ − 629 key + ((prettyPrint) ? " = " : "=") + value
+ − 630 );
+ − 631 }
+ − 632 }
+ − 633 if (!linesToAdd.empty())
+ − 634 {
+ − 635 output.insert(
+ − 636 output.begin() + lastKeyLine,
+ − 637 linesToAdd.begin(),
+ − 638 linesToAdd.end()
+ − 639 );
+ − 640 }
+ − 641 if (writeNewKeys)
+ − 642 {
+ − 643 writeNewKeys = false;
+ − 644 --line;
+ − 645 }
+ − 646 }
+ − 647 }
+ − 648 for (auto const& it : data)
+ − 649 {
+ − 650 auto const& section = it.first;
+ − 651 if (original.has(section))
+ − 652 {
+ − 653 continue;
+ − 654 }
+ − 655 if (prettyPrint && output.size() > 0 && !output.back().empty())
+ − 656 {
+ − 657 output.emplace_back();
+ − 658 }
+ − 659 output.emplace_back("[" + section + "]");
+ − 660 auto const& collection = it.second;
+ − 661 for (auto const& it2 : collection)
+ − 662 {
+ − 663 auto key = it2.first;
+ − 664 auto value = it2.second;
+ − 665 INIStringUtil::replace(key, "=", "\\=");
+ − 666 INIStringUtil::trim(value);
+ − 667 output.emplace_back(
+ − 668 key + ((prettyPrint) ? " = " : "=") + value
+ − 669 );
+ − 670 }
+ − 671 }
+ − 672 return output;
+ − 673 }
+ − 674
+ − 675 public:
+ − 676 bool prettyPrint = false;
+ − 677
+ − 678 INIWriter(std::string const& filename)
+ − 679 : filename(filename)
+ − 680 {
+ − 681 }
+ − 682 ~INIWriter() { }
+ − 683
+ − 684 bool operator<<(INIStructure& data)
+ − 685 {
+ − 686 struct stat buf;
+ − 687 bool fileExists = (stat(filename.c_str(), &buf) == 0);
+ − 688 if (!fileExists)
+ − 689 {
+ − 690 INIGenerator generator(filename);
+ − 691 generator.prettyPrint = prettyPrint;
+ − 692 return generator << data;
+ − 693 }
+ − 694 INIStructure originalData;
+ − 695 T_LineDataPtr lineData;
+ − 696 bool readSuccess = false;
+ − 697 bool fileIsBOM = false;
+ − 698 {
+ − 699 INIReader reader(filename, true);
+ − 700 if ((readSuccess = reader >> originalData))
+ − 701 {
+ − 702 lineData = reader.getLines();
+ − 703 fileIsBOM = reader.isBOM;
+ − 704 }
+ − 705 }
+ − 706 if (!readSuccess)
+ − 707 {
+ − 708 return false;
+ − 709 }
+ − 710 T_LineData output = getLazyOutput(lineData, data, originalData);
+ − 711 std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary);
+ − 712 if (fileWriteStream.is_open())
+ − 713 {
+ − 714 if (fileIsBOM) {
+ − 715 const char utf8_BOM[3] = {
+ − 716 static_cast<char>(0xEF),
+ − 717 static_cast<char>(0xBB),
+ − 718 static_cast<char>(0xBF)
+ − 719 };
+ − 720 fileWriteStream.write(utf8_BOM, 3);
+ − 721 }
+ − 722 if (output.size())
+ − 723 {
+ − 724 auto line = output.begin();
+ − 725 for (;;)
+ − 726 {
+ − 727 fileWriteStream << *line;
+ − 728 if (++line == output.end())
+ − 729 {
+ − 730 break;
+ − 731 }
+ − 732 fileWriteStream << INIStringUtil::endl;
+ − 733 }
+ − 734 }
+ − 735 return true;
+ − 736 }
+ − 737 return false;
+ − 738 }
+ − 739 };
+ − 740
+ − 741 class INIFile
+ − 742 {
+ − 743 private:
+ − 744 std::string filename;
+ − 745
+ − 746 public:
+ − 747 INIFile(std::string const& filename)
+ − 748 : filename(filename)
+ − 749 { }
+ − 750
+ − 751 ~INIFile() { }
+ − 752
+ − 753 bool read(INIStructure& data) const
+ − 754 {
+ − 755 if (data.size())
+ − 756 {
+ − 757 data.clear();
+ − 758 }
+ − 759 if (filename.empty())
+ − 760 {
+ − 761 return false;
+ − 762 }
+ − 763 INIReader reader(filename);
+ − 764 return reader >> data;
+ − 765 }
+ − 766 bool generate(INIStructure const& data, bool pretty = false) const
+ − 767 {
+ − 768 if (filename.empty())
+ − 769 {
+ − 770 return false;
+ − 771 }
+ − 772 INIGenerator generator(filename);
+ − 773 generator.prettyPrint = pretty;
+ − 774 return generator << data;
+ − 775 }
+ − 776 bool write(INIStructure& data, bool pretty = false) const
+ − 777 {
+ − 778 if (filename.empty())
+ − 779 {
+ − 780 return false;
+ − 781 }
+ − 782 INIWriter writer(filename);
+ − 783 writer.prettyPrint = pretty;
+ − 784 return writer << data;
+ − 785 }
+ − 786 };
+ − 787 }
+ − 788
+ − 789 #endif // MINI_INI_H_