mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-14 10:12:59 +02:00
6056d385ed
This should fix rather common problem with mods, where two unrelated mods accidentally use same file name for a config file, leading to very unclear conflict since this result in a file override. Now all config files referenced in mod.json are loaded specifically from filesystem of mod that referenced it. In other words, it is no longer possible for one mod to override config from another mod. As a side effect, this allows mods to use shorter directory layout, e.g. `config/modName/xxx.json` can now be safely replaced with `config/ xxx.json` without fear of broken mod if there is another mod with same path to config. Similarly, now all mods can use `config/translation/ language.json` scheme for translation files Since this is no longer a problem, I've also simplified directory layout of our built-in 'vcmi' mod, by moving all files from `config/vcmi` directory directly to `config` directory. - Overrides for miscellaneous configs like mainmenu.json should works as before - Images / animations (png's or def's) work as before (and may still result in confict) - Rebalance mods work as before and can modify another mod via standard `modName:objectName` syntax
507 lines
10 KiB
C++
507 lines
10 KiB
C++
/*
|
|
* JsonNode.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
|
|
#include "StdInc.h"
|
|
#include "JsonNode.h"
|
|
|
|
#include "JsonParser.h"
|
|
#include "JsonWriter.h"
|
|
#include "filesystem/Filesystem.h"
|
|
|
|
// to avoid duplicating const and non-const code
|
|
template<typename Node>
|
|
Node & resolvePointer(Node & in, const std::string & pointer)
|
|
{
|
|
if(pointer.empty())
|
|
return in;
|
|
assert(pointer[0] == '/');
|
|
|
|
size_t splitPos = pointer.find('/', 1);
|
|
|
|
std::string entry = pointer.substr(1, splitPos - 1);
|
|
std::string remainder = splitPos == std::string::npos ? "" : pointer.substr(splitPos);
|
|
|
|
if(in.getType() == VCMI_LIB_WRAP_NAMESPACE(JsonNode)::JsonType::DATA_VECTOR)
|
|
{
|
|
if(entry.find_first_not_of("0123456789") != std::string::npos) // non-numbers in string
|
|
throw std::runtime_error("Invalid Json pointer");
|
|
|
|
if(entry.size() > 1 && entry[0] == '0') // leading zeros are not allowed
|
|
throw std::runtime_error("Invalid Json pointer");
|
|
|
|
auto index = boost::lexical_cast<size_t>(entry);
|
|
|
|
if(in.Vector().size() > index)
|
|
return in.Vector()[index].resolvePointer(remainder);
|
|
}
|
|
return in[entry].resolvePointer(remainder);
|
|
}
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
static const JsonNode nullNode;
|
|
|
|
class LibClasses;
|
|
class CModHandler;
|
|
|
|
JsonNode::JsonNode(bool boolean)
|
|
: data(boolean)
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(int32_t number)
|
|
: data(static_cast<int64_t>(number))
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(uint32_t number)
|
|
: data(static_cast<int64_t>(number))
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(int64_t number)
|
|
: data(number)
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(double number)
|
|
: data(number)
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(const char * string)
|
|
: data(std::string(string))
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(const std::string & string)
|
|
: data(string)
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(const std::byte * data, size_t datasize, const std::string & fileName)
|
|
: JsonNode(data, datasize, JsonParsingSettings(), fileName)
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings, const std::string & fileName)
|
|
{
|
|
JsonParser parser(data, datasize, parserSettings);
|
|
*this = parser.parse(fileName);
|
|
}
|
|
|
|
JsonNode::JsonNode(const JsonPath & fileURI)
|
|
:JsonNode(fileURI, JsonParsingSettings())
|
|
{
|
|
}
|
|
|
|
JsonNode::JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings)
|
|
{
|
|
auto file = CResourceHandler::get()->load(fileURI)->readAll();
|
|
|
|
JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, parserSettings);
|
|
*this = parser.parse(fileURI.getName());
|
|
}
|
|
|
|
JsonNode::JsonNode(const JsonPath & fileURI, const std::string & modName)
|
|
{
|
|
auto file = CResourceHandler::get(modName)->load(fileURI)->readAll();
|
|
|
|
JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, JsonParsingSettings());
|
|
*this = parser.parse(fileURI.getName());
|
|
}
|
|
|
|
JsonNode::JsonNode(const JsonPath & fileURI, const std::string & modName, bool & isValidSyntax)
|
|
{
|
|
auto file = CResourceHandler::get(modName)->load(fileURI)->readAll();
|
|
|
|
JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, JsonParsingSettings());
|
|
*this = parser.parse(fileURI.getName());
|
|
isValidSyntax = parser.isValid();
|
|
}
|
|
|
|
bool JsonNode::operator==(const JsonNode & other) const
|
|
{
|
|
return data == other.data;
|
|
}
|
|
|
|
bool JsonNode::operator!=(const JsonNode & other) const
|
|
{
|
|
return !(*this == other);
|
|
}
|
|
|
|
JsonNode::JsonType JsonNode::getType() const
|
|
{
|
|
return static_cast<JsonType>(data.index());
|
|
}
|
|
|
|
const std::string & JsonNode::getModScope() const
|
|
{
|
|
return modScope;
|
|
}
|
|
|
|
void JsonNode::setOverrideFlag(bool value)
|
|
{
|
|
overrideFlag = value;
|
|
}
|
|
|
|
bool JsonNode::getOverrideFlag() const
|
|
{
|
|
return overrideFlag;
|
|
}
|
|
|
|
void JsonNode::setModScope(const std::string & metadata, bool recursive)
|
|
{
|
|
modScope = metadata;
|
|
if(recursive)
|
|
{
|
|
switch(getType())
|
|
{
|
|
break;
|
|
case JsonType::DATA_VECTOR:
|
|
{
|
|
for(auto & node : Vector())
|
|
{
|
|
node.setModScope(metadata);
|
|
}
|
|
}
|
|
break;
|
|
case JsonType::DATA_STRUCT:
|
|
{
|
|
for(auto & node : Struct())
|
|
{
|
|
node.second.setModScope(metadata);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void JsonNode::setType(JsonType Type)
|
|
{
|
|
if(getType() == Type)
|
|
return;
|
|
|
|
//float<->int conversion
|
|
if(getType() == JsonType::DATA_FLOAT && Type == JsonType::DATA_INTEGER)
|
|
{
|
|
si64 converted = static_cast<si64>(std::get<double>(data));
|
|
data = JsonData(converted);
|
|
return;
|
|
}
|
|
else if(getType() == JsonType::DATA_INTEGER && Type == JsonType::DATA_FLOAT)
|
|
{
|
|
double converted = static_cast<double>(std::get<si64>(data));
|
|
data = JsonData(converted);
|
|
return;
|
|
}
|
|
|
|
//Set new node type
|
|
switch(Type)
|
|
{
|
|
case JsonType::DATA_NULL:
|
|
data = JsonData();
|
|
break;
|
|
case JsonType::DATA_BOOL:
|
|
data = JsonData(false);
|
|
break;
|
|
case JsonType::DATA_FLOAT:
|
|
data = JsonData(0.0);
|
|
break;
|
|
case JsonType::DATA_STRING:
|
|
data = JsonData(std::string());
|
|
break;
|
|
case JsonType::DATA_VECTOR:
|
|
data = JsonData(JsonVector());
|
|
break;
|
|
case JsonType::DATA_STRUCT:
|
|
data = JsonData(JsonMap());
|
|
break;
|
|
case JsonType::DATA_INTEGER:
|
|
data = JsonData(static_cast<si64>(0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool JsonNode::isNull() const
|
|
{
|
|
return getType() == JsonType::DATA_NULL;
|
|
}
|
|
|
|
bool JsonNode::isNumber() const
|
|
{
|
|
return getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT;
|
|
}
|
|
|
|
bool JsonNode::isString() const
|
|
{
|
|
return getType() == JsonType::DATA_STRING;
|
|
}
|
|
|
|
bool JsonNode::isVector() const
|
|
{
|
|
return getType() == JsonType::DATA_VECTOR;
|
|
}
|
|
|
|
bool JsonNode::isStruct() const
|
|
{
|
|
return getType() == JsonType::DATA_STRUCT;
|
|
}
|
|
|
|
bool JsonNode::containsBaseData() const
|
|
{
|
|
switch(getType())
|
|
{
|
|
case JsonType::DATA_NULL:
|
|
return false;
|
|
case JsonType::DATA_STRUCT:
|
|
for(const auto & elem : Struct())
|
|
{
|
|
if(elem.second.containsBaseData())
|
|
return true;
|
|
}
|
|
return false;
|
|
default:
|
|
//other types (including vector) cannot be extended via merge
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool JsonNode::isCompact() const
|
|
{
|
|
switch(getType())
|
|
{
|
|
case JsonType::DATA_VECTOR:
|
|
for(const JsonNode & elem : Vector())
|
|
{
|
|
if(!elem.isCompact())
|
|
return false;
|
|
}
|
|
return true;
|
|
case JsonType::DATA_STRUCT:
|
|
{
|
|
auto propertyCount = Struct().size();
|
|
if(propertyCount == 0)
|
|
return true;
|
|
else if(propertyCount == 1)
|
|
return Struct().begin()->second.isCompact();
|
|
}
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool JsonNode::TryBoolFromString(bool & success) const
|
|
{
|
|
success = true;
|
|
if(getType() == JsonNode::JsonType::DATA_BOOL)
|
|
return Bool();
|
|
|
|
success = getType() == JsonNode::JsonType::DATA_STRING;
|
|
if(success)
|
|
{
|
|
auto boolParamStr = String();
|
|
boost::algorithm::trim(boolParamStr);
|
|
boost::algorithm::to_lower(boolParamStr);
|
|
success = boolParamStr == "true";
|
|
|
|
if(success)
|
|
return true;
|
|
|
|
success = boolParamStr == "false";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void JsonNode::clear()
|
|
{
|
|
setType(JsonType::DATA_NULL);
|
|
}
|
|
|
|
bool & JsonNode::Bool()
|
|
{
|
|
setType(JsonType::DATA_BOOL);
|
|
return std::get<bool>(data);
|
|
}
|
|
|
|
double & JsonNode::Float()
|
|
{
|
|
setType(JsonType::DATA_FLOAT);
|
|
return std::get<double>(data);
|
|
}
|
|
|
|
si64 & JsonNode::Integer()
|
|
{
|
|
setType(JsonType::DATA_INTEGER);
|
|
return std::get<si64>(data);
|
|
}
|
|
|
|
std::string & JsonNode::String()
|
|
{
|
|
setType(JsonType::DATA_STRING);
|
|
return std::get<std::string>(data);
|
|
}
|
|
|
|
JsonVector & JsonNode::Vector()
|
|
{
|
|
setType(JsonType::DATA_VECTOR);
|
|
return std::get<JsonVector>(data);
|
|
}
|
|
|
|
JsonMap & JsonNode::Struct()
|
|
{
|
|
setType(JsonType::DATA_STRUCT);
|
|
return std::get<JsonMap>(data);
|
|
}
|
|
|
|
bool JsonNode::Bool() const
|
|
{
|
|
static const bool boolDefault = false;
|
|
|
|
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
|
|
|
|
if(getType() == JsonType::DATA_BOOL)
|
|
return std::get<bool>(data);
|
|
|
|
return boolDefault;
|
|
}
|
|
|
|
double JsonNode::Float() const
|
|
{
|
|
static const double floatDefault = 0;
|
|
|
|
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
|
|
|
|
if(getType() == JsonType::DATA_FLOAT)
|
|
return std::get<double>(data);
|
|
|
|
if(getType() == JsonType::DATA_INTEGER)
|
|
return static_cast<double>(std::get<si64>(data));
|
|
|
|
return floatDefault;
|
|
}
|
|
|
|
si64 JsonNode::Integer() const
|
|
{
|
|
static const si64 integerDefault = 0;
|
|
|
|
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
|
|
|
|
if(getType() == JsonType::DATA_INTEGER)
|
|
return std::get<si64>(data);
|
|
|
|
if(getType() == JsonType::DATA_FLOAT)
|
|
return static_cast<si64>(std::get<double>(data));
|
|
|
|
return integerDefault;
|
|
}
|
|
|
|
const std::string & JsonNode::String() const
|
|
{
|
|
static const std::string stringDefault;
|
|
|
|
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING);
|
|
|
|
if(getType() == JsonType::DATA_STRING)
|
|
return std::get<std::string>(data);
|
|
|
|
return stringDefault;
|
|
}
|
|
|
|
const JsonVector & JsonNode::Vector() const
|
|
{
|
|
static const JsonVector vectorDefault;
|
|
|
|
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR);
|
|
|
|
if(getType() == JsonType::DATA_VECTOR)
|
|
return std::get<JsonVector>(data);
|
|
|
|
return vectorDefault;
|
|
}
|
|
|
|
const JsonMap & JsonNode::Struct() const
|
|
{
|
|
static const JsonMap mapDefault;
|
|
|
|
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT);
|
|
|
|
if(getType() == JsonType::DATA_STRUCT)
|
|
return std::get<JsonMap>(data);
|
|
|
|
return mapDefault;
|
|
}
|
|
|
|
JsonNode & JsonNode::operator[](const std::string & child)
|
|
{
|
|
return Struct()[child];
|
|
}
|
|
|
|
const JsonNode & JsonNode::operator[](const std::string & child) const
|
|
{
|
|
auto it = Struct().find(child);
|
|
if(it != Struct().end())
|
|
return it->second;
|
|
return nullNode;
|
|
}
|
|
|
|
JsonNode & JsonNode::operator[](size_t child)
|
|
{
|
|
if(child >= Vector().size())
|
|
Vector().resize(child + 1);
|
|
return Vector()[child];
|
|
}
|
|
|
|
const JsonNode & JsonNode::operator[](size_t child) const
|
|
{
|
|
if(child < Vector().size())
|
|
return Vector()[child];
|
|
|
|
return nullNode;
|
|
}
|
|
|
|
const JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer) const
|
|
{
|
|
return ::resolvePointer(*this, jsonPointer);
|
|
}
|
|
|
|
JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer)
|
|
{
|
|
return ::resolvePointer(*this, jsonPointer);
|
|
}
|
|
|
|
std::vector<std::byte> JsonNode::toBytes() const
|
|
{
|
|
std::string jsonString = toString();
|
|
auto dataBegin = reinterpret_cast<const std::byte *>(jsonString.data());
|
|
auto dataEnd = dataBegin + jsonString.size();
|
|
std::vector<std::byte> result(dataBegin, dataEnd);
|
|
return result;
|
|
}
|
|
|
|
std::string JsonNode::toCompactString() const
|
|
{
|
|
std::ostringstream out;
|
|
JsonWriter writer(out, true);
|
|
writer.writeNode(*this);
|
|
return out.str();
|
|
}
|
|
|
|
std::string JsonNode::toString() const
|
|
{
|
|
std::ostringstream out;
|
|
JsonWriter writer(out, false);
|
|
writer.writeNode(*this);
|
|
return out.str();
|
|
}
|
|
|
|
VCMI_LIB_NAMESPACE_END
|