/* * 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 "HeroBonus.h" #include "Filesystem/CResourceLoader.h" using namespace JsonDetail; static const JsonNode nullNode; JsonNode::JsonNode(JsonType Type): type(DATA_NULL) { setType(Type); } JsonNode::JsonNode(const char *data, size_t datasize): type(DATA_NULL) { JsonParser parser(data, datasize, *this); JsonValidator validator(*this); } JsonNode::JsonNode(ResourceID && fileURI): type(DATA_NULL) { std::string filename = CResourceHandler::get()->getResourceName(fileURI); FILE * file = fopen(filename.c_str(), "rb"); if (!file) { tlog1 << "Failed to open file " << filename << "\n"; perror("Last system error was "); return; } fseek(file, 0, SEEK_END); size_t datasize = ftell(file); fseek(file, 0, SEEK_SET); char *input = new char[datasize]; datasize = fread((void*)input, 1, datasize, file); fclose(file); JsonParser parser(input, datasize, *this); JsonValidator validator(*this); delete [] input; } JsonNode::JsonNode(const JsonNode ©): type(DATA_NULL) { setType(copy.getType()); switch(type) { break; case DATA_NULL: break; case DATA_BOOL: Bool() = copy.Bool(); break; case DATA_FLOAT: Float() = copy.Float(); break; case DATA_STRING: String() = copy.String(); break; case DATA_VECTOR: Vector() = copy.Vector(); break; case DATA_STRUCT: Struct() = copy.Struct(); } } JsonNode::~JsonNode() { setType(DATA_NULL); } void JsonNode::swap(JsonNode &b) { using std::swap; swap(data, b.data); swap(type, b.type); } JsonNode & JsonNode::operator =(JsonNode node) { swap(node); return *this; } bool JsonNode::operator == (const JsonNode &other) const { if (getType() == other.getType()) { switch(type) { break; case DATA_NULL: return true; break; case DATA_BOOL: return Bool() == other.Bool(); break; case DATA_FLOAT: return Float() == other.Float(); break; case DATA_STRING: return String() == other.String(); break; case DATA_VECTOR: return Vector() == other.Vector(); break; case DATA_STRUCT: return Struct() == other.Struct(); } } return false; } bool JsonNode::operator != (const JsonNode &other) const { return !(*this == other); } JsonNode::JsonType JsonNode::getType() const { return type; } void JsonNode::setType(JsonType Type) { if (type == Type) return; //Reset node to NULL if (Type != DATA_NULL) setType(DATA_NULL); switch (type) { break; case DATA_STRING: delete data.String; break; case DATA_VECTOR: delete data.Vector; break; case DATA_STRUCT: delete data.Struct; break; default: break; } //Set new node type type = Type; switch(type) { break; case DATA_NULL: break; case DATA_BOOL: data.Bool = false; break; case DATA_FLOAT: data.Float = 0; break; case DATA_STRING: data.String = new std::string; break; case DATA_VECTOR: data.Vector = new JsonVector; break; case DATA_STRUCT: data.Struct = new JsonMap; } } bool JsonNode::isNull() const { return type == DATA_NULL; } bool & JsonNode::Bool() { setType(DATA_BOOL); return data.Bool; } double & JsonNode::Float() { setType(DATA_FLOAT); return data.Float; } std::string & JsonNode::String() { setType(DATA_STRING); return *data.String; } JsonVector & JsonNode::Vector() { setType(DATA_VECTOR); return *data.Vector; } JsonMap & JsonNode::Struct() { setType(DATA_STRUCT); return *data.Struct; } const bool boolDefault = false; const bool & JsonNode::Bool() const { if (type == DATA_NULL) return boolDefault; assert(type == DATA_BOOL); return data.Bool; } const double floatDefault = 0; const double & JsonNode::Float() const { if (type == DATA_NULL) return floatDefault; assert(type == DATA_FLOAT); return data.Float; } const std::string stringDefault = std::string(); const std::string & JsonNode::String() const { if (type == DATA_NULL) return stringDefault; assert(type == DATA_STRING); return *data.String; } const JsonVector vectorDefault = JsonVector(); const JsonVector & JsonNode::Vector() const { if (type == DATA_NULL) return vectorDefault; assert(type == DATA_VECTOR); return *data.Vector; } const JsonMap mapDefault = JsonMap(); const JsonMap & JsonNode::Struct() const { if (type == DATA_NULL) return mapDefault; assert(type == DATA_STRUCT); return *data.Struct; } JsonNode & JsonNode::operator[](std::string child) { return Struct()[child]; } const JsonNode & JsonNode::operator[](std::string child) const { JsonMap::const_iterator it = Struct().find(child); if (it != Struct().end()) return it->second; return nullNode; } //////////////////////////////////////////////////////////////////////////////// template void JsonWriter::writeContainer(Iterator begin, Iterator end) { if (begin == end) return; prefix += '\t'; end--; while (begin != end) { writeEntry(begin++); out<<",\n"; } writeEntry(begin); out<<"\n"; prefix.resize(prefix.size()-1); } void JsonWriter::writeEntry(JsonMap::const_iterator entry) { out << prefix; writeString(entry->first); out << " : "; writeNode(entry->second); } void JsonWriter::writeEntry(JsonVector::const_iterator entry) { out << prefix; writeNode(*entry); } void JsonWriter::writeString(const std::string &string) { static const std::string escaped = "\"\\/\b\f\n\r\t"; out <<'\"'; size_t pos=0, start=0; for (; pos= '0' && input[pos] <= '9') return extractFloat(node); return error("Value expected!"); } } } bool JsonParser::extractWhitespace(bool verbose) { while (true) { while (pos < input.size() && (ui8)input[pos] <= ' ') { if (input[pos] == '\n') { lineCount++; lineStart = pos+1; } pos++; } if (pos >= input.size() || input[pos] != '/') break; pos++; if (pos == input.size()) break; if (input[pos] == '/') pos++; else error("Comments must consist from two slashes!", true); while (pos < input.size() && input[pos] != '\n') pos++; } if (pos >= input.size() && verbose) return error("Unexpected end of file!"); return true; } bool JsonParser::extractEscaping(std::string &str) { switch(input[pos]) { break; case '\"': str += '\"'; break; case '\\': str += '\\'; break; case '/': str += '/'; break; case 'b': str += '\b'; break; case 'f': str += '\f'; break; case 'n': str += '\n'; break; case 'r': str += '\r'; break; case 't': str += '\t'; break; default: return error("Unknown escape sequence!", true); }; return true; } bool JsonParser::extractString(std::string &str) { if (input[pos] != '\"') return error("String expected!"); pos++; size_t first = pos; while (pos != input.size()) { if (input[pos] == '\"') // Correct end of string { str.append( &input[first], pos-first); pos++; return true; } if (input[pos] == '\\') // Escaping { str.append( &input[first], pos-first); pos++; if (pos == input.size()) break; extractEscaping(str); first = pos + 1; } if (input[pos] == '\n') // end-of-line { str.append( &input[first], pos-first); return error("Closing quote not found!", true); } if ((unsigned char)(input[pos]) < ' ') // control character { str.append( &input[first], pos-first); first = pos+1; error("Illegal character in the string!", true); } pos++; } return error("Unterminated string!"); } bool JsonParser::extractString(JsonNode &node) { std::string str; if (!extractString(str)) return false; node.setType(JsonNode::DATA_STRING); node.String() = str; return true; } bool JsonParser::extractLiteral(const std::string &literal) { if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0) { while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z') || (input[pos]>'A' && input[pos]<'Z'))) pos++; return error("Unknown literal found", true); } pos += literal.size(); return true; } bool JsonParser::extractNull(JsonNode &node) { if (!extractLiteral("null")) return false; node.setType(JsonNode::DATA_NULL); return true; } bool JsonParser::extractTrue(JsonNode &node) { if (!extractLiteral("true")) return false; node.Bool() = true; return true; } bool JsonParser::extractFalse(JsonNode &node) { if (!extractLiteral("false")) return false; node.Bool() = false; return true; } bool JsonParser::extractStruct(JsonNode &node) { node.setType(JsonNode::DATA_STRUCT); pos++; if (!extractWhitespace()) return false; //Empty struct found if (input[pos] == '}') { pos++; return true; } while (true) { if (!extractWhitespace()) return false; std::string key; if (!extractString(key)) return false; if (node.Struct().find(key) != node.Struct().end()) error("Dublicated element encountered!", true); if (!extractSeparator()) return false; if (!extractElement(node.Struct()[key], '}')) return false; if (input[pos] == '}') { pos++; return true; } } } bool JsonParser::extractArray(JsonNode &node) { pos++; node.setType(JsonNode::DATA_VECTOR); if (!extractWhitespace()) return false; //Empty array found if (input[pos] == ']') { pos++; return true; } while (true) { //NOTE: currently 50% of time is this vector resizing. //May be useful to use list during parsing and then swap() all items to vector node.Vector().resize(node.Vector().size()+1); if (!extractElement(node.Vector().back(), ']')) return false; if (input[pos] == ']') { pos++; return true; } } } bool JsonParser::extractElement(JsonNode &node, char terminator) { if (!extractValue(node)) return false; if (!extractWhitespace()) return false; bool comma = (input[pos] == ','); if (comma ) { pos++; if (!extractWhitespace()) return false; } if (input[pos] == terminator) return true; if (!comma) error("Comma expected!", true); return true; } bool JsonParser::extractFloat(JsonNode &node) { assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); bool negative=false; double result=0; if (input[pos] == '-') { pos++; negative = true; } if (input[pos] < '0' || input[pos] > '9') return error("Number expected!"); //Extract integer part while (input[pos] >= '0' && input[pos] <= '9') { result = result*10+(input[pos]-'0'); pos++; } if (input[pos] == '.') { //extract fractional part pos++; double fractMult = 0.1; if (input[pos] < '0' || input[pos] > '9') return error("Decimal part expected!"); while (input[pos] >= '0' && input[pos] <= '9') { result = result + fractMult*(input[pos]-'0'); fractMult /= 10; pos++; } } //TODO: exponential part if (negative) result = -result; node.setType(JsonNode::DATA_FLOAT); node.Float() = result; return true; } bool JsonParser::error(const std::string &message, bool warning) { std::ostringstream stream; std::string type(warning?" warning: ":" error: "); stream << "At line " << lineCount << ", position "< stringToType = boost::assign::map_list_of ("null", JsonNode::DATA_NULL) ("bool", JsonNode::DATA_BOOL) ("number", JsonNode::DATA_FLOAT) ("string", JsonNode::DATA_STRING) ("array", JsonNode::DATA_VECTOR) ("object", JsonNode::DATA_STRUCT); //Check current schema entry for validness and converts "type" string to JsonType bool JsonValidator::validateSchema(JsonNode::JsonType &type, const JsonNode &schema) { if (schema.isNull()) return addMessage("Missing schema for current entry!"); const JsonNode &nodeType = schema["type"]; if (nodeType.isNull()) return addMessage("Entry type is not defined in schema!"); if (nodeType.getType() != JsonNode::DATA_STRING) return addMessage("Entry type must be string!"); std::map::const_iterator iter = stringToType.find(nodeType.String()); if (iter == stringToType.end()) return addMessage("Unknown entry type found!"); type = iter->second; return true; } //Replaces node with default value if needed and calls type-specific validators bool JsonValidator::validateType(JsonNode &node, const JsonNode &schema, JsonNode::JsonType type) { if (node.isNull()) { const JsonNode & defaultValue = schema["default"]; if (defaultValue.isNull()) return addMessage("Null entry without default entry!"); else node = defaultValue; } if (minimize && node == schema["default"]) { node.setType(JsonNode::DATA_NULL); return false; } if (type != node.getType()) { node.setType(JsonNode::DATA_NULL); return addMessage("Type mismatch!"); } if (type == JsonNode::DATA_VECTOR) return validateItems(node, schema["items"]); if (type == JsonNode::DATA_STRUCT) return validateProperties(node, schema["properties"]); return true; } // Basic checks common for any nodes bool JsonValidator::validateNode(JsonNode &node, const JsonNode &schema, const std::string &name) { currentPath.push_back(name); JsonNode::JsonType type = JsonNode::DATA_NULL; if (!validateSchema(type, schema) || !validateType(node, schema, type)) { node.setType(JsonNode::DATA_NULL); currentPath.pop_back(); return false; } currentPath.pop_back(); return true; } //Checks "items" entry from schema (type-specific check for Vector) bool JsonValidator::validateItems(JsonNode &node, const JsonNode &schema) { JsonNode::JsonType type = JsonNode::DATA_NULL; if (!validateSchema(type, schema)) return false; bool result = true; BOOST_FOREACH(JsonNode &entry, node.Vector()) { if (!validateType(entry, schema, type)) { result = false; entry.setType(JsonNode::DATA_NULL); } } return result; } //Checks "propertries" entry from schema (type-specific check for Struct) //Function is similar to merging of two sorted lists - check every entry that present in one of the input nodes bool JsonValidator::validateProperties(JsonNode &node, const JsonNode &schema) { if (schema.isNull()) return addMessage("Properties entry is missing for struct in schema"); JsonMap::iterator nodeIter = node.Struct().begin(); JsonMap::const_iterator schemaIter = schema.Struct().begin(); while (nodeIter != node.Struct().end() && schemaIter != schema.Struct().end()) { if (nodeIter->first < schemaIter->first) //No schema for entry { validateNode(nodeIter->second, nullNode, nodeIter->first); JsonMap::iterator toRemove = nodeIter++; node.Struct().erase(toRemove); } else if (schemaIter->first < nodeIter->first) //No entry { if (!validateNode(node[schemaIter->first], schemaIter->second, schemaIter->first)) node.Struct().erase(schemaIter->first); schemaIter++; } else //both entry and schema are present { JsonMap::iterator current = nodeIter++; if (!validateNode(current->second, schemaIter->second, current->first)) node.Struct().erase(current); schemaIter++; } } while (nodeIter != node.Struct().end()) { validateNode(nodeIter->second, nullNode, nodeIter->first); JsonMap::iterator toRemove = nodeIter++; node.Struct().erase(toRemove); } while (schemaIter != schema.Struct().end()) { if (!validateNode(node[schemaIter->first], schemaIter->second, schemaIter->first)) node.Struct().erase(schemaIter->first); schemaIter++; } return true; } bool JsonValidator::addMessage(const std::string &message) { std::ostringstream stream; stream << "At "; BOOST_FOREACH(const std::string &path, currentPath) stream << path<<"/"; stream << "\t Error: " << message <<"\n"; errors += stream.str(); return false; } JsonValidator::JsonValidator(JsonNode &root, bool Minimize): minimize(Minimize) { if (root.getType() != JsonNode::DATA_STRUCT) return; JsonNode schema; schema.swap(root["schema"]); root.Struct().erase("schema"); if (!schema.isNull()) { validateProperties(root, schema); } //This message is quite annoying now - most files do not have schemas. May be re-enabled later //else // addMessage("Schema not found!", true); //TODO: better way to show errors (like printing file name as well) tlog3<type = it->second; b->val = ability_vec[1].Float(); b->subtype = ability_vec[2].Float(); b->additionalInfo = ability_vec[3].Float(); b->duration = Bonus::PERMANENT; //TODO: handle flags (as integer) b->turnsRemain = 0; return b; } template const T & parseByMap(const std::map & map, const JsonNode * val, std::string err) { static T defaultValue; if (!val->isNull()) { std::string type = val->String(); auto it = map.find(type); if (it == map.end()) { tlog1 << "Error: invalid " << err << type << std::endl; return defaultValue; } else { return it->second; } } else return defaultValue; }; Bonus * JsonUtils::parseBonus (const JsonNode &ability) { Bonus * b = new Bonus(); const JsonNode *value; std::string type = ability["type"].String(); auto it = bonusNameMap.find(type); if (it == bonusNameMap.end()) { tlog1 << "Error: invalid ability type " << type << std::endl; return b; } b->type = it->second; value = &ability["subtype"]; if (!value->isNull()) b->subtype = value->Float(); value = &ability["val"]; if (!value->isNull()) b->val = value->Float(); value = &ability["valueType"]; if (!value->isNull()) b->valType = parseByMap(bonusValueMap, value, "value type "); value = &ability["additionalInfo"]; if (!value->isNull()) b->additionalInfo = value->Float(); value = &ability["turns"]; if (!value->isNull()) b->turnsRemain = value->Float(); value = &ability["sourceID"]; if (!value->isNull()) b->sid = value->Float(); value = &ability["description"]; if (!value->isNull()) b->description = value->String(); value = &ability["effectRange"]; if (!value->isNull()) b->valType = parseByMap(bonusLimitEffect, value, "effect range "); value = &ability["duration"]; if (!value->isNull()) b->valType = parseByMap(bonusDurationMap, value, "duration type "); value = &ability["source"]; if (!value->isNull()) b->valType = parseByMap(bonusSourceMap, value, "source type "); value = &ability["limiter"]; if (!value->isNull()) b->limiter = parseByMap(bonusLimiterMap, value, "limiter type "); value = &ability["propagator"]; if (!value->isNull()) b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type "); return b; } //returns first Key with value equal to given one template Key reverseMapFirst(const Val & val, const std::map map) { BOOST_FOREACH(auto it, map) { if(it.second == val) { return it.first; } } assert(0); return ""; } void JsonUtils::unparseBonus( JsonNode &node, const Bonus * bonus ) { node["type"].String() = reverseMapFirst(bonus->type, bonusNameMap); node["subtype"].Float() = bonus->subtype; node["val"].Float() = bonus->val; node["valueType"].String() = reverseMapFirst(bonus->valType, bonusValueMap); node["additionalInfo"].Float() = bonus->additionalInfo; node["turns"].Float() = bonus->turnsRemain; node["sourceID"].Float() = bonus->source; node["description"].String() = bonus->description; node["effectRange"].String() = reverseMapFirst(bonus->effectRange, bonusLimitEffect); node["duration"].String() = reverseMapFirst(bonus->duration, bonusDurationMap); node["source"].String() = reverseMapFirst(bonus->source, bonusSourceMap); if(bonus->limiter) { node["limiter"].String() = reverseMapFirst(bonus->limiter, bonusLimiterMap); } if(bonus->propagator) { node["propagator"].String() = reverseMapFirst(bonus->propagator, bonusPropagatorMap); } } void JsonUtils::minimize(JsonNode & node, const JsonNode& schema) { JsonValidator validator(node, schema, true); } void JsonUtils::validate(JsonNode & node, const JsonNode& schema) { JsonValidator validator(node, schema, false); } void JsonUtils::merge(JsonNode & dest, JsonNode & source) { if (dest.getType() == JsonNode::DATA_NULL) { std::swap(dest, source); return; } switch (source.getType()) { break; case JsonNode::DATA_NULL: dest.setType(JsonNode::DATA_NULL); break; case JsonNode::DATA_BOOL: std::swap(dest.Bool(), source.Bool()); break; case JsonNode::DATA_FLOAT: std::swap(dest.Float(), source.Float()); break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String()); break; case JsonNode::DATA_VECTOR: { size_t total = std::min(source.Vector().size(), dest.Vector().size()); for (size_t i=0; i< total; i++) merge(dest.Vector()[i], source.Vector()[i]); if (source.Vector().size() < dest.Vector().size()) { //reserve place and *move* data from source to dest source.Vector().reserve(source.Vector().size() + dest.Vector().size()); std::move(source.Vector().begin(), source.Vector().end(), std::back_inserter(dest.Vector())); } } break; case JsonNode::DATA_STRUCT: { //recursively merge all entries from struct BOOST_FOREACH(auto & node, source.Struct()) merge(dest[node.first], node.second); } } } void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source) { // uses copy created in stack to safely merge two nodes merge(dest, source); } JsonNode JsonUtils::assembleFromFiles(std::vector files) { JsonNode result; BOOST_FOREACH(std::string file, files) { JsonNode section(ResourceID(file, EResType::TEXT)); merge(result, section); } return result; }