thebluefish
So once upon a time ChrisMAN posted his dumb AS ini parser. It worked for what it did, but godamn it was Angelscript and so not usable by everybody. So carnalis made his little ini/cfg parser in C++ based off ChrisMAN’s, which did the same thing but in C++ instead of Angeslcript. Following the tradition, I’ve decided to expand on this to a full Urho3D Resource based off their initial work.
Some quick features:
- Works just as any other Urho3D Resource
- Option to toggle case sensitivity
- Supports ‘//’ and ‘#’ comments, ‘;’ comments were left out due to being used by some resources
- Supports ‘=’ and ‘:’ to indicate value pairs
- ‘Smart Save’ option - Replaces only the values, preserving the rest of the file such as comments and whitespacing
- ‘Dumb Save’ option (default) - Writes out brand new file
An example of reading some values from a file:
auto configFile = cache->GetResource<blu::ConfigFile>("settings.cfg");
auto width = configFile->GetInt("engine", "WindowWidth", 1024);
auto height = configFile->GetInt("engine", "WindowHeight", 768);
You can set values:
configFile->Set("engine", "WindowWidth", "800");
configFile->Set("engine", "Windowheight", "600");
configFile->Set("engine", "test", "something dark side");
Then save it back out:
// Saves to application directory
Urho3D::File file(context_, "settings.cfg", Urho3D::FILE_WRITE);
configFile->Save(file, true);
file.Close();
This will be later included in a lib of useful-stuff-to-have, but I am release it independently for now.
License:
The MIT License (MIT)
Copyright (c) 2015 Thebluefish
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
ConfigFile.cpp:
#include "ConfigFile.h"
#include <Urho3D/Core/Context.h>
#include <Urho3D/Core/Variant.h>
#include <Urho3D/IO/File.h>
#include <Urho3D/Container/Str.h>
#include <Urho3D/Container/Vector.h>
#include <Urho3D/Container/HashSet.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/IO/Log.h>
namespace blu
{
ConfigFile::ConfigFile(Urho3D::Context* context, bool caseSensitive)
: Urho3D::Resource(context)
, _caseSensitive(caseSensitive)
{
}
ConfigFile::~ConfigFile()
{
}
void ConfigFile::RegisterObject(Urho3D::Context* context)
{
context->RegisterFactory<ConfigFile>();
}
bool ConfigFile::BeginLoad(Urho3D::Deserializer& source)
{
unsigned dataSize = source.GetSize();
if (!dataSize && !source.GetName().Empty())
{
LOGERROR("Zero sized data in " + source.GetName());
return false;
}
ConfigSection* configSection = new ConfigSection();
_configMap.Push(*configSection);
while (!source.IsEof())
{
// Reads the next line
auto line = source.ReadLine();
// Parse headers
if (line.StartsWith("[") && line.EndsWith("]"))
{
//Urho3D::String sectionName = line.Substring(1, line.Length() - 2);
//currentMap = &_configMap[sectionName];
_configMap.Push(ConfigSection());
configSection = &_configMap.Back();
}
configSection->Push(line);
}
return true;
}
bool ConfigFile::Save(Urho3D::Serializer& dest) const
{
dest.WriteLine("################");
dest.WriteLine("# AUTO-GENERATED");
dest.WriteLine("################");
// Iterate over all sections, printing out the header followed by the properties
for (auto itr = _configMap.Begin(); itr != _configMap.End(); itr++)
{
if (itr->Begin() == itr->End())
continue;
// Don't print section if there's nothing to print
bool hasHeader = false;
for (auto section_itr = itr->Begin(); section_itr != itr->End(); section_itr++)
{
if (ParseComments(*section_itr) != Urho3D::String::EMPTY)
{
hasHeader = true;
dest.WriteLine("");
break;
}
}
auto section_itr = itr->Begin();
// Doesn't print header if it's empty
if (hasHeader)
{
dest.WriteLine("[" + ParseHeader(*section_itr) + "]");
dest.WriteLine("");
}
for (; section_itr != itr->End(); section_itr++)
{
auto line = ParseComments(*section_itr);
Urho3D::String property;
Urho3D::String value;
ParseProperty(line, property, value);
if (property != Urho3D::String::EMPTY && value != Urho3D::String::EMPTY)
dest.WriteLine(property + "=" + value);
}
}
return true;
}
bool ConfigFile::Save(Urho3D::Serializer& dest, bool smartSave) const
{
if (!smartSave)
return Save(dest);
// Iterate over all sections, printing out the header followed by the properties
for (auto itr = _configMap.Begin(); itr != _configMap.End(); itr++)
{
if (itr->Begin() == itr->End())
continue;
for (auto section_itr = itr->Begin(); section_itr != itr->End(); section_itr++)
{
auto line = *section_itr;
dest.WriteLine(line);
}
}
return true;
}
bool ConfigFile::FromString(const Urho3D::String& source)
{
if (source.Empty())
return false;
Urho3D::MemoryBuffer buffer(source.CString(), source.Length());
return Load(buffer);
}
bool ConfigFile::Has(const Urho3D::String& section, const Urho3D::String& parameter)
{
return GetString(section, parameter) != Urho3D::String::EMPTY;
}
const Urho3D::String ConfigFile::GetString(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::String& defaultValue)
{
// Find the correct section
ConfigSection* configSection = 0;
for (auto itr = _configMap.Begin(); itr != _configMap.End(); itr++)
{
if (itr->Begin() == itr->End())
continue;
auto header = *(itr->Begin());
header = ParseHeader(header);
if (_caseSensitive)
{
if (section == header)
{
configSection = &(*itr);
}
}
else
{
if (section.ToLower() == header.ToLower())
{
configSection = &(*itr);
}
}
}
// Section doesn't exist
if (!configSection)
return defaultValue;
for (auto itr = configSection->Begin(); itr != configSection->End(); itr++)
{
Urho3D::String property;
Urho3D::String value;
ParseProperty(*itr, property, value);
if (property == Urho3D::String::EMPTY || value == Urho3D::String::EMPTY)
continue;
if (_caseSensitive)
{
if (parameter == property)
return value;
}
else
{
if (parameter.ToLower() == property.ToLower())
return value;
}
}
return defaultValue;
}
const int ConfigFile::GetInt(const Urho3D::String& section, const Urho3D::String& parameter, const int defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToInt(property);
}
const bool ConfigFile::GetBool(const Urho3D::String& section, const Urho3D::String& parameter, const bool defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToBool(property);
}
const float ConfigFile::GetFloat(const Urho3D::String& section, const Urho3D::String& parameter, const float defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToFloat(property);
}
const Urho3D::Vector2 ConfigFile::GetVector2(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Vector2& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToVector2(property);
}
const Urho3D::Vector3 ConfigFile::GetVector3(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Vector3& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToVector3(property);
}
const Urho3D::Vector4 ConfigFile::GetVector4(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Vector4& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToVector4(property);
}
const Urho3D::Quaternion ConfigFile::GetQuaternion(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Quaternion& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToQuaternion(property);
}
const Urho3D::Color ConfigFile::GetColor(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Color& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToColor(property);
}
const Urho3D::IntRect ConfigFile::GetIntRect(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::IntRect& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToIntRect(property);
}
const Urho3D::IntVector2 ConfigFile::GetIntVector2(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::IntVector2& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToIntVector2(property);
}
const Urho3D::Matrix3 ConfigFile::GetMatrix3(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Matrix3& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToMatrix3(property);
}
const Urho3D::Matrix3x4 ConfigFile::GetMatrix3x4(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Matrix3x4& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToMatrix3x4(property);
}
const Urho3D::Matrix4 ConfigFile::GetMatrix4(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Matrix4& defaultValue)
{
auto property = GetString(section, parameter);
if (property == Urho3D::String::EMPTY)
return defaultValue;
return Urho3D::ToMatrix4(property);
}
void ConfigFile::Set(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::String& value)
{
// Find the correct section
ConfigSection* configSection = 0;
for (auto itr = _configMap.Begin(); itr != _configMap.End(); itr++)
{
if (itr->Begin() == itr->End())
continue;
auto header = *(itr->Begin());
header = ParseHeader(header);
if (_caseSensitive)
{
if (section == header)
{
configSection = &(*itr);
}
}
else
{
if (section.ToLower() == header.ToLower())
{
configSection = &(*itr);
}
}
}
// Section doesn't exist
if (!configSection)
{
// Create section
_configMap.Push(ConfigSection());
configSection = &_configMap.Back();
}
Urho3D::String* line = 0;
unsigned separatorPos = 0;
for (auto itr = configSection->Begin(); itr != configSection->End(); itr++)
{
// Find property seperator
separatorPos = itr->Find("=");
if (separatorPos == Urho3D::String::NPOS)
{
separatorPos = itr->Find(":");
}
// Not a property
if (separatorPos == Urho3D::String::NPOS)
continue;
Urho3D::String workingLine = ParseComments(*itr);
auto oldParameter = workingLine.Substring(0, separatorPos).Trimmed();
auto oldValue = workingLine.Substring(separatorPos + 1).Trimmed();
// Not the correct parameter
if (_caseSensitive ? (oldParameter == parameter) : (oldParameter.ToLower() == parameter.ToLower()))
{
// Replace the value
itr->Replace(itr->Find(oldValue, separatorPos), oldValue.Length(), value);
return;
}
}
// Parameter doesn't exist yet
// We need to find a good place to insert the parameter
// Avoiding lines which are entirely comments or whitespacing
int index = configSection->Size() - 1;
for (int i = index; i >= 0; i--)
{
if (ParseComments((*configSection)[i]) != Urho3D::String::EMPTY)
{
index = i + 1;
break;
}
}
configSection->Insert(index, parameter + "=" + value);
}
// Returns header without bracket
Urho3D::String ConfigFile::ParseHeader(Urho3D::String line) const
{
// Only parse comments outside of headers
unsigned commentPos = 0;
while (commentPos != Urho3D::String::NPOS)
{
// Find next comment
auto lastCommentPos = commentPos;
auto commaPos = line.Find("//", commentPos);
auto hashPos = line.Find("#", commentPos);
commentPos = (commaPos < hashPos) ? commaPos : hashPos;
// Header is behind a comment
if (line.Find("[", lastCommentPos) > commentPos)
{
// Stop parsing this line
break;
}
// Header is before the comment
if (line.Find("[") < commentPos)
{
int startPos = line.Find("[") + 1;
int l1 = line.Find("]");
int length = l1 - startPos;
line = line.Substring(startPos, length);
break;
}
}
line = line.Trimmed();
return line;
}
// property or Empty if no property
void ConfigFile::ParseProperty(Urho3D::String line, Urho3D::String& property, Urho3D::String& value) const
{
line = ParseComments(line);
// Find property seperator
auto separatorPos = line.Find("=");
if (separatorPos == Urho3D::String::NPOS)
{
separatorPos = line.Find(":");
}
// Not a property
if (separatorPos == Urho3D::String::NPOS)
{
property = Urho3D::String::EMPTY;
value = Urho3D::String::EMPTY;
return;
}
property = line.Substring(0, separatorPos).Trimmed();
value = line.Substring(separatorPos + 1).Trimmed();
}
// strips comments and whitespaces
Urho3D::String ConfigFile::ParseComments(Urho3D::String line) const
{
// Comments are normalized
line.Replace("//", "#");
// Ignore comments
unsigned commentPos = line.Find("#");
if (commentPos != Urho3D::String::NPOS)
{
line = line.Substring(0, commentPos);
}
return line;
}
}
ConfigFile.h:
#pragma once
#include <Urho3D/Urho3D.h>
#include <Urho3D/Resource/Resource.h>
#include <Urho3D/Core/Variant.h>
#include <Urho3D/Core/StringUtils.h>
namespace Urho3D
{
class File;
class Variant;
}
namespace blu
{
typedef Urho3D::Vector<Urho3D::String> ConfigSection;
typedef Urho3D::Vector<ConfigSection> ConfigMap;
class ConfigFile : public Urho3D::Resource
{
public:
ConfigFile(Urho3D::Context* context, bool caseSensitive = false);
~ConfigFile();
static void RegisterObject(Urho3D::Context* context);
void SetCaseSensitive(bool caseSensitive) { _caseSensitive = caseSensitive; }
/// Load resource from stream. May be called from a worker thread. Return true if successful.
virtual bool BeginLoad(Urho3D::Deserializer& source);
/// Save resource
virtual bool Save(Urho3D::Serializer& dest) const;
/// Smart Save resource, replacing only the values, keeping whitespacing and comments
virtual bool Save(Urho3D::Serializer& dest, bool smartSave) const;
/// Deserialize from a string. Return true if successful.
bool FromString(const Urho3D::String& source);
const ConfigMap* GetMap() { return &_configMap; }
bool Has(const Urho3D::String& section, const Urho3D::String& parameter);
const Urho3D::String GetString(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::String& defaultValue = Urho3D::String::EMPTY);
const int GetInt(const Urho3D::String& section, const Urho3D::String& parameter, const int defaultValue = 0);
const bool GetBool(const Urho3D::String& section, const Urho3D::String& parameter, const bool defaultValue = false);
const float GetFloat(const Urho3D::String& section, const Urho3D::String& parameter, const float defaultValue = 0.f);
const Urho3D::Vector2 GetVector2(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Vector2& defaultValue = Urho3D::Vector2::ZERO);
const Urho3D::Vector3 GetVector3(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Vector3& defaultValue = Urho3D::Vector3::ZERO);
const Urho3D::Vector4 GetVector4(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Vector4& defaultValue = Urho3D::Vector4::ZERO);
const Urho3D::Quaternion GetQuaternion(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Quaternion& defaultValue = Urho3D::Quaternion::IDENTITY);
const Urho3D::Color GetColor(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Color& defaultValue = Urho3D::Color::WHITE);
const Urho3D::IntRect GetIntRect(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::IntRect& defaultValue = Urho3D::IntRect::ZERO);
const Urho3D::IntVector2 GetIntVector2(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::IntVector2& defaultValue = Urho3D::IntVector2::ZERO);
const Urho3D::Matrix3 GetMatrix3(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Matrix3& defaultValue = Urho3D::Matrix3::IDENTITY);
const Urho3D::Matrix3x4 GetMatrix3x4(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Matrix3x4& defaultValue = Urho3D::Matrix3x4::IDENTITY);
const Urho3D::Matrix4 GetMatrix4(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::Matrix4& defaultValue = Urho3D::Matrix4::IDENTITY);
void Set(const Urho3D::String& section, const Urho3D::String& parameter, const Urho3D::String& value);
protected:
// Returns header without bracket
Urho3D::String ParseHeader(Urho3D::String line) const;
// property or Empty if no property
void ParseProperty(Urho3D::String line, Urho3D::String& property, Urho3D::String& value) const;
// strips comments and whitespaces
Urho3D::String ParseComments(Urho3D::String line) const;
protected:
bool _caseSensitive;
ConfigMap _configMap;
};
}