|
|
/*
|
|
|
OneLoneCoder - DataFile v1.00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
An "easy to use" serialisation/deserialisation class that yields
|
|
|
human readable hierachical files.
|
|
|
|
|
|
License (OLC-3)
|
|
|
~~~~~~~~~~~~~~~
|
|
|
|
|
|
Copyright 2018 - 2024 OneLoneCoder.com
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
modification, are permitted provided that the following conditions
|
|
|
are met:
|
|
|
|
|
|
1. Redistributions or derivations of source code must retain the above
|
|
|
copyright notice, this list of conditions and the following disclaimer.
|
|
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce
|
|
|
the above copyright notice. This list of conditions and the following
|
|
|
disclaimer must be reproduced in the documentation and/or other
|
|
|
materials provided with the distribution.
|
|
|
|
|
|
3. Neither the name of the copyright holder nor the names of its
|
|
|
contributors may be used to endorse or promote products derived
|
|
|
from this software without specific prior written permission.
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
Links
|
|
|
~~~~~
|
|
|
YouTube: https://www.youtube.com/javidx9
|
|
|
Discord: https://discord.gg/WhwHUMV
|
|
|
Twitter: https://www.twitter.com/javidx9
|
|
|
Twitch: https://www.twitch.tv/javidx9
|
|
|
GitHub: https://www.github.com/onelonecoder
|
|
|
Homepage: https://www.onelonecoder.com
|
|
|
|
|
|
Author
|
|
|
~~~~~~
|
|
|
David Barr, aka javidx9, <EFBFBD>OneLoneCoder 2019, 2020, 2021, 2022
|
|
|
|
|
|
*/
|
|
|
|
|
|
#pragma once
|
|
|
#include "olcPixelGameEngine.h"
|
|
|
#include <iostream>
|
|
|
#include <string>
|
|
|
#include <unordered_map>
|
|
|
#include <functional>
|
|
|
#include <fstream>
|
|
|
#include <stack>
|
|
|
#include <sstream>
|
|
|
#include <numeric>
|
|
|
#include "Error.h"
|
|
|
|
|
|
using namespace std::literals;
|
|
|
|
|
|
int operator ""_I(const char*key,std::size_t len);
|
|
|
|
|
|
namespace olc::utils
|
|
|
{
|
|
|
class datafile
|
|
|
{
|
|
|
public:
|
|
|
enum class OverwriteMode{
|
|
|
NO_OVERWRITE,
|
|
|
OVERWRITE,
|
|
|
};
|
|
|
inline datafile() = default;
|
|
|
inline static bool DEBUG_ACCESS_OPTIONS=false;
|
|
|
inline static bool INITIAL_SETUP_COMPLETE=false;
|
|
|
|
|
|
public:
|
|
|
// Sets the String Value of a Property (for a given index)
|
|
|
inline void SetString(const std::string& sString, const size_t nItem = 0)
|
|
|
{
|
|
|
if (nItem >= m_vContent.size())
|
|
|
m_vContent.resize(nItem + 1);
|
|
|
|
|
|
m_vContent[nItem] = sString;
|
|
|
}
|
|
|
|
|
|
// Retrieves the String Value of a Property (for a given index) or ""
|
|
|
inline const std::string&GetString(const size_t nItem = 0) const
|
|
|
{
|
|
|
if (nItem >= m_vContent.size()){
|
|
|
ERR("WARNING! Accesing out-of-bounds list item "<<nItem<<" of "<<lastAccessedProperty<<"!");
|
|
|
return BLANK;
|
|
|
}
|
|
|
else {
|
|
|
return m_vContent[nItem];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Retrieves the String Value of a Property, to include all values that are normally separated by the list separator.
|
|
|
inline std::string GetFullString() const
|
|
|
{
|
|
|
return std::accumulate(m_vContent.begin(),m_vContent.end(),""s,[](std::string str,const std::string&data){
|
|
|
if(str.size()==0){
|
|
|
return std::move(str)+data;
|
|
|
}else{
|
|
|
return std::move(str)+", "+data;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// Retrieves the Real Value of a Property (for a given index) or 0.0
|
|
|
inline const float GetReal(const size_t nItem = 0) const
|
|
|
{
|
|
|
return std::stof(GetString(nItem).c_str());
|
|
|
}
|
|
|
|
|
|
// Sets the Real Value of a Property (for a given index)
|
|
|
inline void SetReal(const float d, const size_t nItem = 0)
|
|
|
{
|
|
|
SetString(std::to_string(d), nItem);
|
|
|
}
|
|
|
|
|
|
// Retrieves the Integer Value of a Property (for a given index) or 0
|
|
|
inline const int64_t GetBigInt(const size_t nItem = 0) const
|
|
|
{
|
|
|
return std::stoll(GetString(nItem).c_str());
|
|
|
}
|
|
|
|
|
|
// Retrieves the Integer Value of a Property (for a given index) or 0
|
|
|
inline const int32_t GetInt(const size_t nItem = 0) const
|
|
|
{
|
|
|
return std::stoi(GetString(nItem).c_str());
|
|
|
}
|
|
|
|
|
|
// Retrieves the Integer Value of a Property (for a given index) or 0
|
|
|
inline const vf2d GetVf2d(const size_t nItem = 0) const
|
|
|
{
|
|
|
return {GetReal(0),GetReal(1)};
|
|
|
}
|
|
|
|
|
|
// Retrieves the Boolean Value of a Property (for a given index) or false
|
|
|
inline const bool GetBool(const size_t nItem = 0) const
|
|
|
{
|
|
|
return GetString(nItem).starts_with('T')||GetString(nItem).starts_with('t');
|
|
|
}
|
|
|
|
|
|
// Sets the Boolean Value of a Property
|
|
|
inline void SetBool(const bool b, const size_t nItem = 0)
|
|
|
{
|
|
|
b?SetString("True",nItem):SetString("False",nItem);
|
|
|
}
|
|
|
|
|
|
// Sets the Integer Value of a Property (for a given index)
|
|
|
inline void SetInt(const int32_t n, const size_t nItem = 0)
|
|
|
{
|
|
|
SetString(std::to_string(n), nItem);
|
|
|
}
|
|
|
|
|
|
// Sets the Long Integer Value of a Property (for a given index)
|
|
|
inline void SetBigInt(const int64_t n, const size_t nItem = 0)
|
|
|
{
|
|
|
SetString(std::to_string(n), nItem);
|
|
|
}
|
|
|
|
|
|
inline Pixel GetPixel(const size_t nItem = 0){
|
|
|
return {uint8_t(GetInt(nItem)),uint8_t(GetInt(nItem+1)),uint8_t(GetInt(nItem+2)),uint8_t(GetInt(nItem+3))};
|
|
|
}
|
|
|
|
|
|
// Returns the number of Values a property consists of
|
|
|
inline size_t GetValueCount() const
|
|
|
{
|
|
|
return m_vContent.size();
|
|
|
}
|
|
|
|
|
|
inline const std::vector<std::string>&GetValues()const
|
|
|
{
|
|
|
return m_vContent;
|
|
|
}
|
|
|
|
|
|
inline const std::unordered_map<std::string,size_t>&GetKeys()const{
|
|
|
return m_mapObjects;
|
|
|
}
|
|
|
|
|
|
//This function is slightly expensive due to a filtering function required to remove all comments!
|
|
|
inline std::vector<std::pair<std::string,datafile>> GetOrderedKeys(){
|
|
|
std::vector<std::pair<std::string,datafile>>orderedKeys;
|
|
|
std::copy_if(m_vecObjects.begin(),m_vecObjects.end(),std::back_inserter(orderedKeys),[&](const std::pair<std::string,datafile>&data){return !datafile::IsComment(data);});
|
|
|
return orderedKeys;
|
|
|
}
|
|
|
|
|
|
// Checks if a property exists - useful to avoid creating properties
|
|
|
// via reading them, though non-essential
|
|
|
inline bool HasProperty(const std::string& sName)
|
|
|
{
|
|
|
size_t x = sName.find_first_of('.');
|
|
|
if (x != std::string::npos)
|
|
|
{
|
|
|
std::string sProperty = sName.substr(0, x);
|
|
|
if(HasProperty(sProperty)){
|
|
|
return GetProperty(sName.substr(0, x)).HasProperty(sName.substr(x + 1, sName.size()));
|
|
|
}else{
|
|
|
return false;
|
|
|
}
|
|
|
}else{
|
|
|
return m_mapObjects.count(sName) > 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Access a datafile via a convenient name - "root.node.something.property"
|
|
|
inline datafile& GetProperty(const std::string& name)
|
|
|
{
|
|
|
size_t x = name.find_first_of('.');
|
|
|
if (x != std::string::npos)
|
|
|
{
|
|
|
std::string sProperty = name.substr(0, x);
|
|
|
lastAccessedProperty=sProperty;
|
|
|
if (HasProperty(sProperty))
|
|
|
return operator[](sProperty).GetProperty(name.substr(x + 1, name.size()));
|
|
|
else {
|
|
|
ERR("WARNING! Could not read Property " << sProperty << "! Potential bugs may occur.")
|
|
|
return operator[](sProperty);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
return operator[](name);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Access a numbered element - "node[23]", or "root[56].node"
|
|
|
inline datafile& GetIndexedProperty(const std::string& name, const size_t nIndex)
|
|
|
{
|
|
|
return GetProperty(name + "[" + std::to_string(nIndex) + "]");
|
|
|
}
|
|
|
|
|
|
public:
|
|
|
// Writes a "datafile" node (and all of its child nodes and properties) recursively
|
|
|
// to a file.
|
|
|
inline static bool Write(const datafile& n, const std::string& sFileName, const std::string& sIndent = "\t", const char sListSep = ',')
|
|
|
{
|
|
|
// Cache indentation level
|
|
|
size_t nIndentCount = 0;
|
|
|
// Cache sperator string for convenience
|
|
|
std::string sSeperator = std::string(1, sListSep) + " ";
|
|
|
|
|
|
// Fully specified lambda, because this lambda is recursive!
|
|
|
std::function<void(const datafile&, std::ofstream&)> write = [&](const datafile& n, std::ofstream& file)
|
|
|
{
|
|
|
// Lambda creates string given indentation preferences
|
|
|
auto indent = [&](const std::string& sString, const size_t nCount)
|
|
|
{
|
|
|
std::string sOut;
|
|
|
for (size_t n = 0; n < nCount; n++) sOut += sString;
|
|
|
return sOut;
|
|
|
};
|
|
|
|
|
|
// Iterate through each property of this node
|
|
|
for (auto const& property : n.m_vecObjects)
|
|
|
{
|
|
|
// Does property contain any sub objects?
|
|
|
if (property.second.m_vecObjects.empty())
|
|
|
{
|
|
|
// No, so it's an assigned field and should just be written. If the property
|
|
|
// is flagged as comment, it has no assignment potential. First write the
|
|
|
// property name
|
|
|
file << indent(sIndent, nIndentCount) << property.first << (property.second.m_bIsComment ? "" : " = ");
|
|
|
|
|
|
// Second, write the property value (or values, seperated by provided
|
|
|
// separation charater
|
|
|
size_t nItems = property.second.GetValueCount();
|
|
|
for (size_t i = 0; i < property.second.GetValueCount(); i++)
|
|
|
{
|
|
|
// If the Value being written, in string form, contains the separation
|
|
|
// character, then the value must be written inside quotation marks. Note,
|
|
|
// that if the Value is the last of a list of Values for a property, it is
|
|
|
// not suffixed with the separator
|
|
|
size_t x = property.second.GetString(i).find_first_of(sListSep);
|
|
|
if (x != std::string::npos)
|
|
|
{
|
|
|
// Value contains separator, so wrap in quotes
|
|
|
file << "\"" << property.second.GetString(i) << "\"" << ((nItems > 1) ? sSeperator : "");
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Value does not contain separator, so just write out
|
|
|
file << property.second.GetString(i) << ((nItems > 1) ? sSeperator : "");
|
|
|
}
|
|
|
nItems--;
|
|
|
}
|
|
|
|
|
|
// Property written, move to next line
|
|
|
file << "\n";
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Yes, property has properties of its own, so it's a node
|
|
|
// Force a new line and write out the node's name
|
|
|
file << "\n" << indent(sIndent, nIndentCount) << property.first << "\n";
|
|
|
// Open braces, and update indentation
|
|
|
file << indent(sIndent, nIndentCount) << "{\n";
|
|
|
nIndentCount++;
|
|
|
// Recursively write that node
|
|
|
write(property.second, file);
|
|
|
// Node written, so close braces
|
|
|
file << indent(sIndent, nIndentCount) << "}\n\n";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// We've finished writing out a node, regardless of state, our indentation
|
|
|
// must decrease, unless we're top level
|
|
|
if (nIndentCount > 0) nIndentCount--;
|
|
|
};
|
|
|
|
|
|
// Start Here! Open the file for writing
|
|
|
std::ofstream file(sFileName);
|
|
|
if (file.is_open())
|
|
|
{
|
|
|
// Write the file starting form the supplied node
|
|
|
write(n, file);
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
inline static bool Read(datafile& n, const std::string& sFileName, const char sListSep = ',', const OverwriteMode mode=OverwriteMode::NO_OVERWRITE)
|
|
|
{
|
|
|
bool previousSetupState=INITIAL_SETUP_COMPLETE;
|
|
|
INITIAL_SETUP_COMPLETE=false;
|
|
|
// Open the file!
|
|
|
std::ifstream file(sFileName);
|
|
|
if (file.is_open())
|
|
|
{
|
|
|
// These variables are outside of the read loop, as we will
|
|
|
// need to refer to previous iteration values in certain conditions
|
|
|
std::string sPropName = "";
|
|
|
std::string sPropValue = "";
|
|
|
|
|
|
// The file is fundamentally structured as a stack, so we will read it
|
|
|
// in a such, but note the data structure in memory is not explicitly
|
|
|
// stored in a stack, but one is constructed implicitly via the nodes
|
|
|
// owning other nodes (aka a tree)
|
|
|
|
|
|
// I dont want to accidentally create copies all over the place, nor do
|
|
|
// I want to use pointer syntax, so being a bit different and stupidly
|
|
|
// using std::reference_wrapper, so I can store references to datafile
|
|
|
// nodes in a std::container.
|
|
|
std::stack<std::reference_wrapper<datafile>> stkPath;
|
|
|
stkPath.push(n);
|
|
|
|
|
|
|
|
|
// Read file line by line and process
|
|
|
while (!file.eof())
|
|
|
{
|
|
|
// Read line
|
|
|
std::string line;
|
|
|
std::getline(file, line);
|
|
|
|
|
|
// This little lambda removes whitespace from
|
|
|
// beginning and end of supplied string
|
|
|
auto trim = [](std::string& s)
|
|
|
{
|
|
|
s.erase(0, s.find_first_not_of(" \t\n\r\f\v"));
|
|
|
s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1);
|
|
|
};
|
|
|
|
|
|
trim(line);
|
|
|
|
|
|
// If line has content
|
|
|
if (!line.empty())
|
|
|
{
|
|
|
// Test if its a comment...
|
|
|
if (line[0] == '#')
|
|
|
{
|
|
|
// ...it is a comment, so ignore
|
|
|
datafile comment;
|
|
|
comment.m_bIsComment = true;
|
|
|
stkPath.top().get().m_vecObjects.push_back({ line, comment });
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// ...it is content, so parse. Firstly, find if the line
|
|
|
// contains an assignment. If it does then it's a property...
|
|
|
size_t x = line.find_first_of('=');
|
|
|
if (x != std::string::npos)
|
|
|
{
|
|
|
// ...so split up the property into a name, and its values!
|
|
|
|
|
|
// Extract the property name, which is all characters up to
|
|
|
// first assignment, trim any whitespace from ends
|
|
|
sPropName = line.substr(0, x);
|
|
|
trim(sPropName);
|
|
|
auto&top=stkPath.top().get();
|
|
|
if(mode==OverwriteMode::NO_OVERWRITE&&stkPath.top().get().HasProperty(sPropName))ERR(std::format("WARNING! Duplicate key found! Key {} already exists! Duplicate line: {}",sPropName,line));
|
|
|
|
|
|
// Extract the property value, which is all characters after
|
|
|
// the first assignment operator, trim any whitespace from ends
|
|
|
sPropValue = line.substr(x + 1, line.size());
|
|
|
trim(sPropValue);
|
|
|
|
|
|
// The value may be in list form: a, b, c, d, e, f etc and some of those
|
|
|
// elements may exist in quotes a, b, c, "d, e", f. So we need to iterate
|
|
|
// character by character and break up the value
|
|
|
bool bInQuotes = false;
|
|
|
bool bEscapeChar = false;
|
|
|
std::string sToken;
|
|
|
size_t nTokenCount = 0;
|
|
|
for (const auto c : sPropValue)
|
|
|
{
|
|
|
if (bEscapeChar&&c == 'n') //Parse a newline.
|
|
|
{
|
|
|
sToken.append(1, '\n');
|
|
|
bEscapeChar=false;
|
|
|
continue;
|
|
|
}
|
|
|
bEscapeChar=false;
|
|
|
// Is character a quote...
|
|
|
if (c == '\"')
|
|
|
{
|
|
|
// ...yes, so toggle quote state
|
|
|
bInQuotes = !bInQuotes;
|
|
|
}
|
|
|
else
|
|
|
if (c == '\\')
|
|
|
{
|
|
|
// ...yes, so toggle quote state
|
|
|
bEscapeChar=true;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// ...no, so proceed creating token. If we are in quote state
|
|
|
// then just append characters until we exit quote state.
|
|
|
if (bInQuotes)
|
|
|
{
|
|
|
sToken.append(1, c);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Is the character our seperator? If it is
|
|
|
if (c == sListSep)
|
|
|
{
|
|
|
// Clean up the token
|
|
|
trim(sToken);
|
|
|
// Add it to the vector of values for this property
|
|
|
stkPath.top().get()[sPropName].SetString(sToken, nTokenCount);
|
|
|
// Reset our token state
|
|
|
sToken.clear();
|
|
|
nTokenCount++;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// It isnt, so just append to token
|
|
|
sToken.append(1, c);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Any residual characters at this point just make up the final token,
|
|
|
// so clean it up and add it to the vector of values
|
|
|
if (!sToken.empty())
|
|
|
{
|
|
|
trim(sToken);
|
|
|
stkPath.top().get()[sPropName].SetString(sToken, nTokenCount);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// ...but if it doesnt, then it's something structural
|
|
|
if (line[0] == '{')
|
|
|
{
|
|
|
// Open brace, so push this node to stack, subsequent properties
|
|
|
// will belong to the new node
|
|
|
stkPath.push(stkPath.top().get()[sPropName]);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if (line[0] == '}')
|
|
|
{
|
|
|
// Close brace, so this node has been defined, pop it from the
|
|
|
// stack
|
|
|
stkPath.pop();
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Line is a property with no assignment. Who knows whether this is useful,
|
|
|
// but we can simply add it as a valueless property...
|
|
|
sPropName = line;
|
|
|
// ...actually it is useful, as valuless properties are typically
|
|
|
// going to be the names of new datafile nodes on the next iteration
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Close and exit!
|
|
|
file.close();
|
|
|
INITIAL_SETUP_COMPLETE=previousSetupState;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// File not found, so fail
|
|
|
ERR("WARNING! Could not open file "<<sFileName<<"!");
|
|
|
INITIAL_SETUP_COMPLETE=previousSetupState;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public:
|
|
|
inline void Reset(){
|
|
|
INITIAL_SETUP_COMPLETE=false;
|
|
|
DEBUG_ACCESS_OPTIONS=false;
|
|
|
m_mapObjects.clear();
|
|
|
m_vecObjects.clear();
|
|
|
m_vContent.clear();
|
|
|
lastAccessedProperty="";
|
|
|
BLANK="";
|
|
|
m_bIsComment=false;
|
|
|
}
|
|
|
inline datafile& operator[](const std::string& name)
|
|
|
{
|
|
|
// Check if this "node"'s map already contains an object with this name...
|
|
|
if (m_mapObjects.count(name) == 0)
|
|
|
{
|
|
|
if(INITIAL_SETUP_COMPLETE){
|
|
|
ERR("WARNING! Key "<<name<<" does not exist in datafile!") //We're not going to allow manual creation of nodes.
|
|
|
}
|
|
|
// ...it did not! So create this object in the map. First get a vector id
|
|
|
// and link it with the name in the unordered_map
|
|
|
m_mapObjects[name] = m_vecObjects.size();
|
|
|
// then creating the new, blank object in the vector of objects
|
|
|
m_vecObjects.push_back({ name, datafile() });
|
|
|
}
|
|
|
|
|
|
// ...it exists! so return the object, by getting its index from the map, and using that
|
|
|
// index to look up a vector element.
|
|
|
return m_vecObjects[m_mapObjects[name]].second;
|
|
|
}
|
|
|
|
|
|
inline auto begin(){
|
|
|
return GetKeys().begin();
|
|
|
}
|
|
|
inline auto end(){
|
|
|
return GetKeys().end();
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
// The "list of strings" that make up a property value
|
|
|
std::vector<std::string> m_vContent;
|
|
|
|
|
|
// Linkage to create "ordered" unordered_map. We have a vector of
|
|
|
// "properties", and the index to a specific element is mapped.
|
|
|
std::vector<std::pair<std::string, datafile>> m_vecObjects;
|
|
|
std::unordered_map<std::string, size_t> m_mapObjects;
|
|
|
|
|
|
inline static std::string lastAccessedProperty="";
|
|
|
inline static std::string BLANK="";
|
|
|
|
|
|
inline static bool IsComment(const std::pair<std::string,datafile>&data){
|
|
|
return data.first.length()>0&&data.first[0]=='#';
|
|
|
}
|
|
|
|
|
|
protected:
|
|
|
// Used to identify if a property is a comment or not, not user facing
|
|
|
bool m_bIsComment = false;
|
|
|
};
|
|
|
|
|
|
class datafilestringdata
|
|
|
{
|
|
|
std::reference_wrapper<datafile>data;
|
|
|
std::string key;
|
|
|
public:
|
|
|
inline datafilestringdata(datafile&dat,std::string key)
|
|
|
:data(dat),key(key){};
|
|
|
std::string operator[](int index){return data.get().GetProperty(key).GetString(index);};
|
|
|
inline std::string concat(){
|
|
|
std::string finalStr="";
|
|
|
for(const std::string&str:data.get().GetProperty(key).GetValues()){
|
|
|
if(finalStr.length()>0)finalStr+=", ";
|
|
|
finalStr+=str;
|
|
|
}
|
|
|
return finalStr;
|
|
|
}
|
|
|
};
|
|
|
class datafilebooldata
|
|
|
{
|
|
|
std::reference_wrapper<datafile>data;
|
|
|
std::string key;
|
|
|
public:
|
|
|
inline datafilebooldata(datafile&dat,std::string key)
|
|
|
:data(dat),key(key){};
|
|
|
int operator[](int index){
|
|
|
return data.get().GetProperty(key).GetBool(index);
|
|
|
};
|
|
|
};
|
|
|
class datafileintdata
|
|
|
{
|
|
|
std::reference_wrapper<datafile>data;
|
|
|
std::string key;
|
|
|
public:
|
|
|
inline datafileintdata(datafile&dat,std::string key)
|
|
|
:data(dat),key(key){};
|
|
|
int operator[](int index){
|
|
|
return data.get().GetProperty(key).GetInt(index);
|
|
|
};
|
|
|
};
|
|
|
class datafilefloatdata
|
|
|
{
|
|
|
std::reference_wrapper<datafile>data;
|
|
|
std::string key;
|
|
|
public:
|
|
|
inline datafilefloatdata(datafile&dat,std::string key)
|
|
|
:data(dat),key(key){};
|
|
|
float operator[](int index){return float(data.get().GetProperty(key).GetReal(index));};
|
|
|
};
|
|
|
class datafiledoubledata
|
|
|
{
|
|
|
std::reference_wrapper<datafile>data;
|
|
|
std::string key;
|
|
|
public:
|
|
|
inline datafiledoubledata(datafile&dat,std::string key)
|
|
|
:data(dat),key(key){};
|
|
|
double operator[](int index){return data.get().GetProperty(key).GetReal(index);};
|
|
|
};
|
|
|
} |