|
@@ -1,5 +1,5 @@
|
|
|
/*
|
|
|
- * JsonNode.cpp, part of VCMI engine
|
|
|
+ * JsonUtils.cpp, part of VCMI engine
|
|
|
*
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
*
|
|
@@ -9,437 +9,27 @@
|
|
|
*/
|
|
|
|
|
|
#include "StdInc.h"
|
|
|
-#include "JsonNode.h"
|
|
|
-
|
|
|
-#include "ScopeGuard.h"
|
|
|
-
|
|
|
-#include "bonuses/BonusParams.h"
|
|
|
-#include "bonuses/Bonus.h"
|
|
|
-#include "bonuses/Limiters.h"
|
|
|
-#include "bonuses/Propagators.h"
|
|
|
-#include "bonuses/Updaters.h"
|
|
|
-#include "filesystem/Filesystem.h"
|
|
|
-#include "modding/IdentifierStorage.h"
|
|
|
-#include "VCMI_Lib.h" //for identifier resolution
|
|
|
-#include "CGeneralTextHandler.h"
|
|
|
-#include "JsonDetail.h"
|
|
|
-#include "constants/StringConstants.h"
|
|
|
-#include "battle/BattleHex.h"
|
|
|
-
|
|
|
-namespace
|
|
|
-{
|
|
|
-// 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 remainer = 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(remainer);
|
|
|
- }
|
|
|
- return in[entry].resolvePointer(remainer);
|
|
|
-}
|
|
|
-}
|
|
|
+#include "JsonBonus.h"
|
|
|
+
|
|
|
+#include "JsonValidator.h"
|
|
|
+
|
|
|
+#include "../ScopeGuard.h"
|
|
|
+#include "../bonuses/BonusParams.h"
|
|
|
+#include "../bonuses/Bonus.h"
|
|
|
+#include "../bonuses/Limiters.h"
|
|
|
+#include "../bonuses/Propagators.h"
|
|
|
+#include "../bonuses/Updaters.h"
|
|
|
+#include "../filesystem/Filesystem.h"
|
|
|
+#include "../modding/IdentifierStorage.h"
|
|
|
+#include "../VCMI_Lib.h" //for identifier resolution
|
|
|
+#include "../CGeneralTextHandler.h"
|
|
|
+#include "../constants/StringConstants.h"
|
|
|
+#include "../battle/BattleHex.h"
|
|
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
|
-using namespace JsonDetail;
|
|
|
-
|
|
|
-class LibClasses;
|
|
|
-class CModHandler;
|
|
|
-
|
|
|
static const JsonNode nullNode;
|
|
|
|
|
|
-JsonNode::JsonNode(JsonType Type)
|
|
|
-{
|
|
|
- setType(Type);
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode::JsonNode(const std::byte *data, size_t datasize)
|
|
|
- :JsonNode(reinterpret_cast<const char*>(data), datasize)
|
|
|
-{}
|
|
|
-
|
|
|
-JsonNode::JsonNode(const char *data, size_t datasize)
|
|
|
-{
|
|
|
- JsonParser parser(data, datasize);
|
|
|
- *this = parser.parse("<unknown>");
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode::JsonNode(const JsonPath & fileURI)
|
|
|
-{
|
|
|
- auto file = CResourceHandler::get()->load(fileURI)->readAll();
|
|
|
-
|
|
|
- JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
|
|
|
- *this = parser.parse(fileURI.getName());
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI)
|
|
|
-{
|
|
|
- auto file = CResourceHandler::get(idx)->load(fileURI)->readAll();
|
|
|
-
|
|
|
- JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
|
|
|
- *this = parser.parse(fileURI.getName());
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax)
|
|
|
-{
|
|
|
- auto file = CResourceHandler::get()->load(fileURI)->readAll();
|
|
|
-
|
|
|
- JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
|
|
|
- *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());
|
|
|
-}
|
|
|
-
|
|
|
-void JsonNode::setMeta(const std::string & metadata, bool recursive)
|
|
|
-{
|
|
|
- meta = metadata;
|
|
|
- if (recursive)
|
|
|
- {
|
|
|
- switch (getType())
|
|
|
- {
|
|
|
- break; case JsonType::DATA_VECTOR:
|
|
|
- {
|
|
|
- for(auto & node : Vector())
|
|
|
- {
|
|
|
- node.setMeta(metadata);
|
|
|
- }
|
|
|
- }
|
|
|
- break; case JsonType::DATA_STRUCT:
|
|
|
- {
|
|
|
- for(auto & node : Struct())
|
|
|
- {
|
|
|
- node.second.setMeta(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)
|
|
|
- {
|
|
|
- break; case JsonType::DATA_NULL: data = JsonData();
|
|
|
- break; case JsonType::DATA_BOOL: data = JsonData(false);
|
|
|
- break; case JsonType::DATA_FLOAT: data = JsonData(static_cast<double>(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));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-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);
|
|
|
-}
|
|
|
-
|
|
|
-const bool boolDefault = false;
|
|
|
-bool JsonNode::Bool() const
|
|
|
-{
|
|
|
- assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
|
|
|
-
|
|
|
- if (getType() == JsonType::DATA_BOOL)
|
|
|
- return std::get<bool>(data);
|
|
|
-
|
|
|
- return boolDefault;
|
|
|
-}
|
|
|
-
|
|
|
-const double floatDefault = 0;
|
|
|
-double JsonNode::Float() const
|
|
|
-{
|
|
|
- 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;
|
|
|
-}
|
|
|
-
|
|
|
-const si64 integerDefault = 0;
|
|
|
-si64 JsonNode::Integer() const
|
|
|
-{
|
|
|
- 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 stringDefault = std::string();
|
|
|
-const std::string & JsonNode::String() const
|
|
|
-{
|
|
|
- assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING);
|
|
|
-
|
|
|
- if (getType() == JsonType::DATA_STRING)
|
|
|
- return std::get<std::string>(data);
|
|
|
-
|
|
|
- return stringDefault;
|
|
|
-}
|
|
|
-
|
|
|
-const JsonVector vectorDefault = JsonVector();
|
|
|
-const JsonVector & JsonNode::Vector() const
|
|
|
-{
|
|
|
- assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR);
|
|
|
-
|
|
|
- if (getType() == JsonType::DATA_VECTOR)
|
|
|
- return std::get<JsonVector>(data);
|
|
|
-
|
|
|
- return vectorDefault;
|
|
|
-}
|
|
|
-
|
|
|
-const JsonMap mapDefault = JsonMap();
|
|
|
-const JsonMap & JsonNode::Struct() const
|
|
|
-{
|
|
|
- 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(bool compact) const
|
|
|
-{
|
|
|
- std::string jsonString = toJson(compact);
|
|
|
- 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::toJson(bool compact) const
|
|
|
-{
|
|
|
- std::ostringstream out;
|
|
|
- JsonWriter writer(out, compact);
|
|
|
- writer.writeNode(*this);
|
|
|
- return out.str();
|
|
|
-}
|
|
|
-
|
|
|
-///JsonUtils
|
|
|
-
|
|
|
static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
|
|
|
{
|
|
|
if (node.isNull())
|
|
@@ -1145,7 +735,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
|
|
|
if (!value->isNull())
|
|
|
{
|
|
|
//ALL_CREATURES old propagator compatibility
|
|
|
- if(value->String() == "ALL_CREATURES")
|
|
|
+ if(value->String() == "ALL_CREATURES")
|
|
|
{
|
|
|
logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter");
|
|
|
b->addLimiter(std::make_shared<CreatureLevelLimiter>());
|
|
@@ -1182,7 +772,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
|
|
|
CSelector base = Selector::none;
|
|
|
for(const auto & andN : value->Vector())
|
|
|
base = base.Or(parseSelector(andN));
|
|
|
-
|
|
|
+
|
|
|
ret = ret.And(base);
|
|
|
}
|
|
|
|
|
@@ -1192,7 +782,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
|
|
|
CSelector base = Selector::none;
|
|
|
for(const auto & andN : value->Vector())
|
|
|
base = base.Or(parseSelector(andN));
|
|
|
-
|
|
|
+
|
|
|
ret = ret.And(base.Not());
|
|
|
}
|
|
|
|
|
@@ -1237,7 +827,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
|
|
|
else if(src)
|
|
|
ret = ret.And(Selector::sourceTypeSel(*src));
|
|
|
|
|
|
-
|
|
|
+
|
|
|
value = &ability["targetSourceType"];
|
|
|
if(value->isString())
|
|
|
{
|
|
@@ -1276,378 +866,4 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-//returns first Key with value equal to given one
|
|
|
-template<class Key, class Val>
|
|
|
-Key reverseMapFirst(const Val & val, const std::map<Key, Val> & map)
|
|
|
-{
|
|
|
- for(auto it : map)
|
|
|
- {
|
|
|
- if(it.second == val)
|
|
|
- {
|
|
|
- return it.first;
|
|
|
- }
|
|
|
- }
|
|
|
- assert(0);
|
|
|
- return "";
|
|
|
-}
|
|
|
-
|
|
|
-static JsonNode getDefaultValue(const JsonNode & schema, std::string fieldName)
|
|
|
-{
|
|
|
- const JsonNode & fieldProps = schema["properties"][fieldName];
|
|
|
-
|
|
|
-#if defined(VCMI_IOS)
|
|
|
- if (!fieldProps["defaultIOS"].isNull())
|
|
|
- return fieldProps["defaultIOS"];
|
|
|
-#elif defined(VCMI_ANDROID)
|
|
|
- if (!fieldProps["defaultAndroid"].isNull())
|
|
|
- return fieldProps["defaultAndroid"];
|
|
|
-#elif defined(VCMI_WINDOWS)
|
|
|
- if (!fieldProps["defaultWindows"].isNull())
|
|
|
- return fieldProps["defaultWindows"];
|
|
|
-#endif
|
|
|
-
|
|
|
-#if !defined(VCMI_MOBILE)
|
|
|
- if (!fieldProps["defaultDesktop"].isNull())
|
|
|
- return fieldProps["defaultDesktop"];
|
|
|
-#endif
|
|
|
- return fieldProps["default"];
|
|
|
-}
|
|
|
-
|
|
|
-static void eraseOptionalNodes(JsonNode & node, const JsonNode & schema)
|
|
|
-{
|
|
|
- assert(schema["type"].String() == "object");
|
|
|
-
|
|
|
- std::set<std::string> foundEntries;
|
|
|
-
|
|
|
- for(const auto & entry : schema["required"].Vector())
|
|
|
- foundEntries.insert(entry.String());
|
|
|
-
|
|
|
- vstd::erase_if(node.Struct(), [&](const auto & node){
|
|
|
- return !vstd::contains(foundEntries, node.first);
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-static void minimizeNode(JsonNode & node, const JsonNode & schema)
|
|
|
-{
|
|
|
- if (schema["type"].String() != "object")
|
|
|
- return;
|
|
|
-
|
|
|
- for(const auto & entry : schema["required"].Vector())
|
|
|
- {
|
|
|
- const std::string & name = entry.String();
|
|
|
- minimizeNode(node[name], schema["properties"][name]);
|
|
|
-
|
|
|
- if (vstd::contains(node.Struct(), name) && node[name] == getDefaultValue(schema, name))
|
|
|
- node.Struct().erase(name);
|
|
|
- }
|
|
|
- eraseOptionalNodes(node, schema);
|
|
|
-}
|
|
|
-
|
|
|
-static void maximizeNode(JsonNode & node, const JsonNode & schema)
|
|
|
-{
|
|
|
- // "required" entry can only be found in object/struct
|
|
|
- if (schema["type"].String() != "object")
|
|
|
- return;
|
|
|
-
|
|
|
- // check all required entries that have default version
|
|
|
- for(const auto & entry : schema["required"].Vector())
|
|
|
- {
|
|
|
- const std::string & name = entry.String();
|
|
|
-
|
|
|
- if (node[name].isNull() && !getDefaultValue(schema, name).isNull())
|
|
|
- node[name] = getDefaultValue(schema, name);
|
|
|
-
|
|
|
- maximizeNode(node[name], schema["properties"][name]);
|
|
|
- }
|
|
|
-
|
|
|
- eraseOptionalNodes(node, schema);
|
|
|
-}
|
|
|
-
|
|
|
-void JsonUtils::minimize(JsonNode & node, const std::string & schemaName)
|
|
|
-{
|
|
|
- minimizeNode(node, getSchema(schemaName));
|
|
|
-}
|
|
|
-
|
|
|
-void JsonUtils::maximize(JsonNode & node, const std::string & schemaName)
|
|
|
-{
|
|
|
- maximizeNode(node, getSchema(schemaName));
|
|
|
-}
|
|
|
-
|
|
|
-bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName)
|
|
|
-{
|
|
|
- std::string log = Validation::check(schemaName, node);
|
|
|
- if (!log.empty())
|
|
|
- {
|
|
|
- logMod->warn("Data in %s is invalid!", dataName);
|
|
|
- logMod->warn(log);
|
|
|
- logMod->trace("%s json: %s", dataName, node.toJson(true));
|
|
|
- }
|
|
|
- return log.empty();
|
|
|
-}
|
|
|
-
|
|
|
-const JsonNode & getSchemaByName(const std::string & name)
|
|
|
-{
|
|
|
- // cached schemas to avoid loading json data multiple times
|
|
|
- static std::map<std::string, JsonNode> loadedSchemas;
|
|
|
-
|
|
|
- if (vstd::contains(loadedSchemas, name))
|
|
|
- return loadedSchemas[name];
|
|
|
-
|
|
|
- auto filename = JsonPath::builtin("config/schemas/" + name);
|
|
|
-
|
|
|
- if (CResourceHandler::get()->existsResource(filename))
|
|
|
- {
|
|
|
- loadedSchemas[name] = JsonNode(filename);
|
|
|
- return loadedSchemas[name];
|
|
|
- }
|
|
|
-
|
|
|
- logMod->error("Error: missing schema with name %s!", name);
|
|
|
- assert(0);
|
|
|
- return nullNode;
|
|
|
-}
|
|
|
-
|
|
|
-const JsonNode & JsonUtils::getSchema(const std::string & URI)
|
|
|
-{
|
|
|
- size_t posColon = URI.find(':');
|
|
|
- size_t posHash = URI.find('#');
|
|
|
- std::string filename;
|
|
|
- if(posColon == std::string::npos)
|
|
|
- {
|
|
|
- filename = URI.substr(0, posHash);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- std::string protocolName = URI.substr(0, posColon);
|
|
|
- filename = URI.substr(posColon + 1, posHash - posColon - 1) + ".json";
|
|
|
- if(protocolName != "vcmi")
|
|
|
- {
|
|
|
- logMod->error("Error: unsupported URI protocol for schema: %s", URI);
|
|
|
- return nullNode;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // check if json pointer if present (section after hash in string)
|
|
|
- if(posHash == std::string::npos || posHash == URI.size() - 1)
|
|
|
- {
|
|
|
- auto const & result = getSchemaByName(filename);
|
|
|
- if (result.isNull())
|
|
|
- logMod->error("Error: missing schema %s", URI);
|
|
|
- return result;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- auto const & result = getSchemaByName(filename).resolvePointer(URI.substr(posHash + 1));
|
|
|
- if (result.isNull())
|
|
|
- logMod->error("Error: missing schema %s", URI);
|
|
|
- return result;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, bool copyMeta)
|
|
|
-{
|
|
|
- if (dest.getType() == JsonNode::JsonType::DATA_NULL)
|
|
|
- {
|
|
|
- std::swap(dest, source);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- switch (source.getType())
|
|
|
- {
|
|
|
- case JsonNode::JsonType::DATA_NULL:
|
|
|
- {
|
|
|
- dest.clear();
|
|
|
- break;
|
|
|
- }
|
|
|
- case JsonNode::JsonType::DATA_BOOL:
|
|
|
- case JsonNode::JsonType::DATA_FLOAT:
|
|
|
- case JsonNode::JsonType::DATA_INTEGER:
|
|
|
- case JsonNode::JsonType::DATA_STRING:
|
|
|
- case JsonNode::JsonType::DATA_VECTOR:
|
|
|
- {
|
|
|
- std::swap(dest, source);
|
|
|
- break;
|
|
|
- }
|
|
|
- case JsonNode::JsonType::DATA_STRUCT:
|
|
|
- {
|
|
|
- if(!ignoreOverride && vstd::contains(source.flags, "override"))
|
|
|
- {
|
|
|
- std::swap(dest, source);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- if (copyMeta)
|
|
|
- dest.meta = source.meta;
|
|
|
-
|
|
|
- //recursively merge all entries from struct
|
|
|
- for(auto & node : source.Struct())
|
|
|
- merge(dest[node.first], node.second, ignoreOverride);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride, bool copyMeta)
|
|
|
-{
|
|
|
- // uses copy created in stack to safely merge two nodes
|
|
|
- merge(dest, source, ignoreOverride, copyMeta);
|
|
|
-}
|
|
|
-
|
|
|
-void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
|
|
|
-{
|
|
|
- JsonNode inheritedNode(base);
|
|
|
- merge(inheritedNode, descendant, true, true);
|
|
|
- std::swap(descendant, inheritedNode);
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode JsonUtils::intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty)
|
|
|
-{
|
|
|
- if(nodes.empty())
|
|
|
- return nullNode;
|
|
|
-
|
|
|
- JsonNode result = nodes[0];
|
|
|
- for(int i = 1; i < nodes.size(); i++)
|
|
|
- {
|
|
|
- if(result.isNull())
|
|
|
- break;
|
|
|
- result = JsonUtils::intersect(result, nodes[i], pruneEmpty);
|
|
|
- }
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode JsonUtils::intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty)
|
|
|
-{
|
|
|
- if(a.getType() == JsonNode::JsonType::DATA_STRUCT && b.getType() == JsonNode::JsonType::DATA_STRUCT)
|
|
|
- {
|
|
|
- // intersect individual properties
|
|
|
- JsonNode result(JsonNode::JsonType::DATA_STRUCT);
|
|
|
- for(const auto & property : a.Struct())
|
|
|
- {
|
|
|
- if(vstd::contains(b.Struct(), property.first))
|
|
|
- {
|
|
|
- JsonNode propertyIntersect = JsonUtils::intersect(property.second, b.Struct().find(property.first)->second);
|
|
|
- if(pruneEmpty && !propertyIntersect.containsBaseData())
|
|
|
- continue;
|
|
|
- result[property.first] = propertyIntersect;
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // not a struct - same or different, no middle ground
|
|
|
- if(a == b)
|
|
|
- return a;
|
|
|
- }
|
|
|
- return nullNode;
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode JsonUtils::difference(const JsonNode & node, const JsonNode & base)
|
|
|
-{
|
|
|
- auto addsInfo = [](JsonNode diff) -> bool
|
|
|
- {
|
|
|
- switch(diff.getType())
|
|
|
- {
|
|
|
- case JsonNode::JsonType::DATA_NULL:
|
|
|
- return false;
|
|
|
- case JsonNode::JsonType::DATA_STRUCT:
|
|
|
- return !diff.Struct().empty();
|
|
|
- default:
|
|
|
- return true;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- if(node.getType() == JsonNode::JsonType::DATA_STRUCT && base.getType() == JsonNode::JsonType::DATA_STRUCT)
|
|
|
- {
|
|
|
- // subtract individual properties
|
|
|
- JsonNode result(JsonNode::JsonType::DATA_STRUCT);
|
|
|
- for(const auto & property : node.Struct())
|
|
|
- {
|
|
|
- if(vstd::contains(base.Struct(), property.first))
|
|
|
- {
|
|
|
- const JsonNode propertyDifference = JsonUtils::difference(property.second, base.Struct().find(property.first)->second);
|
|
|
- if(addsInfo(propertyDifference))
|
|
|
- result[property.first] = propertyDifference;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- result[property.first] = property.second;
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- if(node == base)
|
|
|
- return nullNode;
|
|
|
- }
|
|
|
- return node;
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files)
|
|
|
-{
|
|
|
- bool isValid = false;
|
|
|
- return assembleFromFiles(files, isValid);
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files, bool & isValid)
|
|
|
-{
|
|
|
- isValid = true;
|
|
|
- JsonNode result;
|
|
|
-
|
|
|
- for(const auto & file : files)
|
|
|
- {
|
|
|
- bool isValidFile = false;
|
|
|
- JsonNode section(JsonPath::builtinTODO(file), isValidFile);
|
|
|
- merge(result, section);
|
|
|
- isValid |= isValidFile;
|
|
|
- }
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
|
|
|
-{
|
|
|
- JsonNode result;
|
|
|
- JsonPath resID = JsonPath::builtinTODO(filename);
|
|
|
-
|
|
|
- for(auto & loader : CResourceHandler::get()->getResourcesWithName(resID))
|
|
|
- {
|
|
|
- // FIXME: some way to make this code more readable
|
|
|
- auto stream = loader->load(resID);
|
|
|
- std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
|
|
|
- stream->read(textData.get(), stream->getSize());
|
|
|
-
|
|
|
- JsonNode section(reinterpret_cast<char *>(textData.get()), stream->getSize());
|
|
|
- merge(result, section);
|
|
|
- }
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-DLL_LINKAGE JsonNode JsonUtils::boolNode(bool value)
|
|
|
-{
|
|
|
- JsonNode node;
|
|
|
- node.Bool() = value;
|
|
|
- return node;
|
|
|
-}
|
|
|
-
|
|
|
-DLL_LINKAGE JsonNode JsonUtils::floatNode(double value)
|
|
|
-{
|
|
|
- JsonNode node;
|
|
|
- node.Float() = value;
|
|
|
- return node;
|
|
|
-}
|
|
|
-
|
|
|
-DLL_LINKAGE JsonNode JsonUtils::stringNode(const std::string & value)
|
|
|
-{
|
|
|
- JsonNode node;
|
|
|
- node.String() = value;
|
|
|
- return node;
|
|
|
-}
|
|
|
-
|
|
|
-DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value)
|
|
|
-{
|
|
|
- JsonNode node;
|
|
|
- node.Integer() = value;
|
|
|
- return node;
|
|
|
-}
|
|
|
-
|
|
|
VCMI_LIB_NAMESPACE_END
|