You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

975 lines
22 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
Copyright (c) 2013 Johannes Häggqvist
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.
*/
#ifdef _WINDLL
#define JzonAPI __declspec(dllexport)
#endif
#ifdef _MSC_VER
#define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
#endif
#include "Jzon.h"
#include <algorithm>
#include <array>
#include <fstream>
#include <sstream>
#include <stack>
namespace Jzon {
class FormatInterpreter {
public:
FormatInterpreter() {
SetFormat(NoFormat);
}
explicit FormatInterpreter(const Format& format) {
SetFormat(format);
}
void SetFormat(const Format& format) {
this->format = format;
indentationChar = (format.useTabs ? '\t' : ' ');
spacing = (format.spacing ? " " : "");
newline = (format.newline ? "\n" : spacing);
}
std::string GetIndentation(size_t level) const {
if (format.newline)
return std::string(format.indentSize * level, indentationChar);
return "";
}
inline const std::string& GetNewline() const {
return newline;
}
inline const std::string& GetSpacing() const {
return spacing;
}
private:
Format format;
char indentationChar;
std::string newline;
std::string spacing;
};
inline bool IsWhitespace(char c) {
return (c == '\n' || c == ' ' || c == '\t' || c == '\r' || c == '\f');
}
inline bool IsNumber(char c) {
return ((c >= '0' && c <= '9') || c == '.' || c == '-');
}
Object& Node::AsObject() {
if (IsObject())
return dynamic_cast<Object&>(*this);
throw TypeException();
}
const Object& Node::AsObject() const {
if (IsObject())
return dynamic_cast<const Object&>(*this);
throw TypeException();
}
Array& Node::AsArray() {
if (IsArray())
return dynamic_cast<Array&>(*this);
throw TypeException();
}
const Array& Node::AsArray() const {
if (IsArray())
return dynamic_cast<const Array&>(*this);
throw TypeException();
}
Value& Node::AsValue() {
if (IsValue())
return dynamic_cast<Value&>(*this);
throw TypeException();
}
const Value& Node::AsValue() const {
if (IsValue())
return dynamic_cast<const Value&>(*this);
throw TypeException();
}
Node::Type Node::DetermineType(const std::string& json) {
auto jsonIt = std::find_if(json.begin(), json.end(), IsWhitespace);
if (jsonIt == json.end())
return T_VALUE;
switch (*jsonIt) {
case '{':
return T_OBJECT;
case '[':
return T_ARRAY;
default:
return T_VALUE;
}
}
Value::Value() {
SetNull();
}
Value::Value(const Value& rhs) {
Set(rhs);
}
Value::Value(const Node& rhs) {
const Value& value = rhs.AsValue();
Set(value);
}
Value::Value(ValueType type, const std::string& value) {
Set(type, value);
}
Value::Value(const std::string& value) {
Set(value);
}
Value::Value(const char* value) {
Set(value);
}
Value::Value(const int value) {
Set(value);
}
Value::Value(const float value) {
Set(value);
}
Value::Value(const double value) {
Set(value);
}
Value::Value(const bool value) {
Set(value);
}
Node::Type Value::GetType() const {
return T_VALUE;
}
Value::ValueType Value::GetValueType() const {
return type;
}
std::string Value::ToString() const {
if (IsNull()) {
return "null";
}
return valueStr;
}
int Value::ToInt() const {
if (IsNumber()) {
std::stringstream sstr(valueStr);
int val = 0;
sstr >> val;
return val;
}
return 0;
}
float Value::ToFloat() const {
if (IsNumber()) {
std::stringstream sstr(valueStr);
float val = 0;
sstr >> val;
return val;
}
return 0.F;
}
double Value::ToDouble() const {
if (IsNumber()) {
std::stringstream sstr(valueStr);
double val = 0;
sstr >> val;
return val;
}
return 0.0;
}
bool Value::ToBool() const {
if (IsBool()) {
return (valueStr == "true");
}
return false;
}
void Value::SetNull() {
valueStr = "";
type = VT_NULL;
}
void Value::Set(const Value& value) {
if (this != &value) {
valueStr = value.valueStr;
type = value.type;
}
}
void Value::Set(ValueType type, const std::string& value) {
valueStr = value;
this->type = type;
}
void Value::Set(const std::string& value) {
valueStr = UnescapeString(value);
type = VT_STRING;
}
void Value::Set(const char* value) {
valueStr = UnescapeString(std::string(value));
type = VT_STRING;
}
void Value::Set(const int value) {
std::stringstream sstr;
sstr << value;
valueStr = sstr.str();
type = VT_NUMBER;
}
void Value::Set(const float value) {
std::stringstream sstr;
sstr << value;
valueStr = sstr.str();
type = VT_NUMBER;
}
void Value::Set(const double value) {
std::stringstream sstr;
sstr << value;
valueStr = sstr.str();
type = VT_NUMBER;
}
void Value::Set(const bool value) {
valueStr = value ? "true" : "false";
type = VT_BOOL;
}
Value& Value::operator=(const Value& rhs) {
if (this != &rhs)
Set(rhs);
return *this;
}
Value& Value::operator=(const Node& rhs) {
if (this != &rhs)
Set(rhs.AsValue());
return *this;
}
Value& Value::operator=(const std::string& rhs) {
Set(rhs);
return *this;
}
Value& Value::operator=(const char* rhs) {
Set(rhs);
return *this;
}
Value& Value::operator=(const int rhs) {
Set(rhs);
return *this;
}
Value& Value::operator=(const float rhs) {
Set(rhs);
return *this;
}
Value& Value::operator=(const double rhs) {
Set(rhs);
return *this;
}
Value& Value::operator=(const bool rhs) {
Set(rhs);
return *this;
}
bool Value::operator==(const Value& other) const {
return ((type == other.type) && (valueStr == other.valueStr));
}
bool Value::operator!=(const Value& other) const {
return !(*this == other);
}
Node* Value::GetCopy() const {
return new Value(*this);
}
// This is not the most beautiful place for these, but it'll do
using chrPair = struct {
char first;
const char* second;
};
static constexpr std::array<chrPair, 8> chars{
chrPair{'\\', "\\\\"}, chrPair{'/', "\\/"}, chrPair{'\"', "\\\""}, chrPair{'\n', "\\n"},
chrPair{'\t', "\\t"}, chrPair{'\b', "\\b"}, chrPair{'\f', "\\f"}, chrPair{'\r', "\\r"},
};
static constexpr char nullUnescaped = '\0';
static constexpr const char* nullEscaped = "\0\0";
const char* const& getEscaped(const char& c) {
for (auto&& chr : chars) {
if (chr.first == c) {
return chr.second;
}
}
return nullEscaped;
}
const char& getUnescaped(const char& c1, const char& c2) {
for (auto&& chr : chars) {
if (c1 == chars[0].first && c2 == chars[1].first) {
return chr.first;
}
}
return nullUnescaped;
}
std::string Value::EscapeString(const std::string& value) {
std::string escaped;
for (auto&& c : value) {
auto&& a = getEscaped(c);
if (a[0] != '\0') {
escaped += a[0];
escaped += a[1];
} else {
escaped += c;
}
}
return escaped;
}
std::string Value::UnescapeString(const std::string& value) {
std::string unescaped;
for (auto it = value.cbegin(); it != value.cend(); ++it) {
const char& c = (*it);
char c2 = '\0';
if (it + 1 != value.end())
c2 = *(it + 1);
const char& a = getUnescaped(c, c2);
if (a != '\0') {
unescaped += a;
if (it + 1 != value.end())
++it;
} else {
unescaped += c;
}
}
return unescaped;
}
Object::Object(const Object& other) {
std::transform(other.children.begin(), other.children.end(), std::back_inserter(children),
[](const NamedNodePtr& child) { return NamedNodePtr(child.first, child.second->GetCopy()); });
}
Object::Object(const Node& other) {
std::transform(other.AsObject().children.begin(), other.AsObject().children.end(), std::back_inserter(children),
[](const NamedNodePtr& child) { return NamedNodePtr(child.first, child.second->GetCopy()); });
}
Object::~Object() {
Clear();
}
Node::Type Object::GetType() const {
return T_OBJECT;
}
void Object::Add(const std::string& name, Node& node) {
children.emplace_back(name, node.GetCopy());
}
void Object::Add(const std::string& name, const Value& node) {
children.emplace_back(name, new Value(node));
}
void Object::Remove(const std::string& name) {
for (auto it = children.cbegin(); it != children.cend(); ++it) {
if ((*it).first == name) {
delete (*it).second;
children.erase(it);
break;
}
}
}
void Object::Clear() {
for (auto&& child : children) {
delete child.second;
child.second = nullptr;
}
children.clear();
}
Object::iterator Object::begin() {
if (!children.empty())
return {&children.front()};
return {nullptr};
}
Object::const_iterator Object::begin() const {
if (!children.empty())
return {&children.front()};
return {nullptr};
}
Object::iterator Object::end() {
if (!children.empty())
return {&children.back() + 1};
return {nullptr};
}
Object::const_iterator Object::end() const {
if (!children.empty())
return {&children.back() + 1};
return {nullptr};
}
bool Object::Has(const std::string& name) const {
return std::any_of(children.begin(), children.end(), [&](const NamedNodePtr& child) { return child.first == name; });
}
size_t Object::GetCount() const {
return children.size();
}
Node& Object::Get(const std::string& name) const {
for (auto&& child : children) {
if (child.first == name) {
return *child.second;
}
}
throw NotFoundException();
}
Node* Object::GetCopy() const {
return new Object(*this);
}
Array::Array(const Array& other) {
for (auto&& value : other.children) {
children.push_back(value->GetCopy());
}
}
Array::Array(const Node& other) {
const Array& array = other.AsArray();
for (auto&& value : array.children) {
children.push_back(value->GetCopy());
}
}
Array::~Array() {
Clear();
}
Node::Type Array::GetType() const {
return T_ARRAY;
}
void Array::Add(Node& node) {
children.push_back(node.GetCopy());
}
void Array::Add(const Value& node) {
children.push_back(new Value(node));
}
void Array::Remove(size_t index) {
if (index < children.size()) {
auto it = children.begin() + index;
delete (*it);
children.erase(it);
}
}
void Array::Clear() {
for (auto&& child : children) {
delete child;
child = nullptr;
}
children.clear();
}
Array::iterator Array::begin() {
if (!children.empty())
return {&children.front()};
return {nullptr};
}
Array::const_iterator Array::begin() const {
if (!children.empty())
return {&children.front()};
return {nullptr};
}
Array::iterator Array::end() {
if (!children.empty())
return {&children.back() + 1};
return {nullptr};
}
Array::const_iterator Array::end() const {
if (!children.empty())
return {&children.back() + 1};
return {nullptr};
}
size_t Array::GetCount() const {
return children.size();
}
Node& Array::Get(size_t index) const {
if (index < children.size()) {
return *children.at(index);
}
throw NotFoundException();
}
Node* Array::GetCopy() const {
return new Array(*this);
}
FileWriter::FileWriter(std::string filename) : filename(std::move(filename)) {
}
void FileWriter::WriteFile(const std::string& filename, const Node& root, const Format& format) {
FileWriter writer(filename);
writer.Write(root, format);
}
void FileWriter::Write(const Node& root, const Format& format) {
Writer writer(root, format);
writer.Write();
std::fstream file(filename.c_str(), std::ios::out | std::ios::trunc);
file << writer.GetResult();
file.close();
}
FileReader::FileReader(const std::string& filename) {
if (!loadFile(filename, json)) {
error = "Failed to load file";
}
}
bool FileReader::ReadFile(const std::string& filename, Node& node) {
FileReader reader(filename);
return reader.Read(node);
}
bool FileReader::Read(Node& node) {
if (!error.empty())
return false;
Parser parser(node, json);
if (!parser.Parse()) {
error = parser.GetError();
return false;
}
return true;
}
Node::Type FileReader::DetermineType() {
return Node::DetermineType(json);
}
const std::string& FileReader::GetError() const {
return error;
}
bool FileReader::loadFile(const std::string& filename, std::string& json) {
std::fstream file(filename.c_str(), std::ios::in | std::ios::binary);
if (!file.is_open()) {
return false;
}
file.seekg(0, std::ios::end);
std::ios::pos_type size = file.tellg();
file.seekg(0, std::ios::beg);
json.resize(static_cast<std::string::size_type>(size), '\0');
file.read(&json[0], size);
return true;
}
Writer::Writer(const Node& root, const Format& format) : fi(new FormatInterpreter), root(root) {
SetFormat(format);
}
Writer::~Writer() {
delete fi;
fi = nullptr;
}
void Writer::SetFormat(const Format& format) {
fi->SetFormat(format);
}
const std::string& Writer::Write() {
result.clear();
writeNode(root, 0);
return result;
}
const std::string& Writer::GetResult() const {
return result;
}
void Writer::writeNode(const Node& node, size_t level) {
switch (node.GetType()) {
case Node::T_OBJECT:
writeObject(node.AsObject(), level);
break;
case Node::T_ARRAY:
writeArray(node.AsArray(), level);
break;
case Node::T_VALUE:
writeValue(node.AsValue());
break;
}
}
void Writer::writeObject(const Object& node, size_t level) {
result += "{" + fi->GetNewline();
for (auto it = node.begin(); it != node.end(); ++it) {
const std::string& name = (*it).first;
// const Node &value = (*it).second;
if (it != node.begin())
result += "," + fi->GetNewline();
result += fi->GetIndentation(level + 1) + "\"" + name + "\"" + ":" + fi->GetSpacing();
writeNode((*it).second, level + 1);
}
result += fi->GetNewline() + fi->GetIndentation(level) + "}";
}
void Writer::writeArray(const Array& node, size_t level) {
result += "[" + fi->GetNewline();
for (auto it = node.begin(); it != node.end(); ++it) {
const Node& value = (*it);
if (it != node.begin())
result += "," + fi->GetNewline();
result += fi->GetIndentation(level + 1);
writeNode(value, level + 1);
}
result += fi->GetNewline() + fi->GetIndentation(level) + "]";
}
void Writer::writeValue(const Value& node) {
if (node.IsString()) {
result += "\"" + Value::EscapeString(node.ToString()) + "\"";
} else {
result += node.ToString();
}
}
Parser::Parser(Node& root) : root(root) {
}
Parser::Parser(Node& root, const std::string& json) : root(root) {
SetJson(json);
}
void Parser::SetJson(const std::string& json) {
this->json = json;
jsonSize = json.size();
}
bool Parser::Parse() {
cursor = 0;
tokenize();
bool success = assemble();
return success;
}
const std::string& Parser::GetError() const {
return error;
}
void Parser::tokenize() {
Token token = T_UNKNOWN;
std::string valueBuffer;
bool saveBuffer;
for (; cursor < jsonSize; ++cursor) {
char c = json.at(cursor);
if (IsWhitespace(c))
continue;
saveBuffer = true;
switch (c) {
case '{': {
token = T_OBJ_BEGIN;
break;
}
case '}': {
token = T_OBJ_END;
break;
}
case '[': {
token = T_ARRAY_BEGIN;
break;
}
case ']': {
token = T_ARRAY_END;
break;
}
case ',': {
token = T_SEPARATOR_NODE;
break;
}
case ':': {
token = T_SEPARATOR_NAME;
break;
}
case '"': {
token = T_VALUE;
readString();
break;
}
case '/': {
char p = peek();
if (p == '*') {
jumpToCommentEnd();
} else if (p == '/') {
jumpToNext('\n');
}
break;
}
default: {
valueBuffer += c;
saveBuffer = false;
break;
}
}
if ((saveBuffer || cursor == jsonSize - 1) && (!valueBuffer.empty())) // Always save buffer on the last character
{
if (interpretValue(valueBuffer)) {
tokens.push(T_VALUE);
} else {
// Store the unknown token, so we can show it to the user
data.emplace(Value::VT_STRING, valueBuffer);
tokens.push(T_UNKNOWN);
}
valueBuffer.clear();
}
// Push the token last so that any
// value token will get pushed first
// from above.
// If saveBuffer is false, it means that
// we are in the middle of a value, so we
// don't want to push any tokens now.
if (saveBuffer) {
tokens.push(token);
}
}
}
bool Parser::assemble() {
std::stack<std::pair<std::string, Node*>> nodeStack;
std::string name;
Token token;
while (!tokens.empty()) {
token = tokens.front();
tokens.pop();
switch (token) {
case T_UNKNOWN: {
const std::string& unknownToken = data.front().second;
error = "Unknown token: " + unknownToken;
data.pop();
return false;
}
case T_OBJ_BEGIN: {
Node* node = nullptr;
if (nodeStack.empty()) {
if (!root.IsObject()) {
error = "The given root node is not an object";
return false;
}
node = &root;
} else {
node = new Object;
}
nodeStack.emplace(name, node);
name.clear();
break;
}
case T_ARRAY_BEGIN: {
Node* node = nullptr;
if (nodeStack.empty()) {
if (!root.IsArray()) {
error = "The given root node is not an array";
return false;
}
node = &root;
} else {
node = new Array;
}
nodeStack.emplace(name, node);
name.clear();
break;
}
case T_OBJ_END:
case T_ARRAY_END: {
if (nodeStack.empty()) {
error = "Found end of object or array without beginning";
return false;
}
if (token == T_OBJ_END && !nodeStack.top().second->IsObject()) {
error = "Mismatched end and beginning of object";
return false;
}
if (token == T_ARRAY_END && !nodeStack.top().second->IsArray()) {
error = "Mismatched end and beginning of array";
return false;
}
std::string name = nodeStack.top().first;
Node* node = nodeStack.top().second;
nodeStack.pop();
if (!nodeStack.empty()) {
if (nodeStack.top().second->IsObject()) {
nodeStack.top().second->AsObject().Add(name, *node);
} else if (nodeStack.top().second->IsArray()) {
nodeStack.top().second->AsArray().Add(*node);
} else {
error = "Can only add elements to objects and arrays";
return false;
}
delete node;
node = nullptr;
}
break;
}
case T_VALUE: {
if (!tokens.empty() && tokens.front() == T_SEPARATOR_NAME) {
tokens.pop();
if (data.front().first != Value::VT_STRING) {
error = "A name has to be a string";
return false;
}
name = data.front().second;
data.pop();
} else {
Node* node = nullptr;
if (nodeStack.empty()) {
if (!root.IsValue()) {
error = "The given root node is not a value";
return false;
}
node = &root;
} else {
node = new Value;
}
if (data.front().first == Value::VT_STRING) {
dynamic_cast<Value*>(node)->Set(data.front().second); // This method calls UnescapeString()
} else {
dynamic_cast<Value*>(node)->Set(data.front().first, data.front().second);
}
data.pop();
if (!nodeStack.empty()) {
if (nodeStack.top().second->IsObject())
nodeStack.top().second->AsObject().Add(name, *node);
else if (nodeStack.top().second->IsArray())
nodeStack.top().second->AsArray().Add(*node);
delete node;
node = nullptr;
name.clear();
} else {
nodeStack.emplace(name, node);
name.clear();
}
}
break;
}
case T_SEPARATOR_NAME:
case T_SEPARATOR_NODE:
break;
}
}
return true;
}
char Parser::peek() {
if (cursor < jsonSize - 1) {
return json.at(cursor + 1);
}
return '\0';
}
void Parser::jumpToNext(char c) {
++cursor;
while (cursor < jsonSize && json.at(cursor) != c)
++cursor;
}
void Parser::jumpToCommentEnd() {
++cursor;
char c1 = '\0';
for (; cursor < jsonSize; ++cursor) {
char c2 = json.at(cursor);
if (c1 == '*' && c2 == '/')
break;
c1 = c2;
}
}
void Parser::readString() {
if (json.at(cursor) != '"')
return;
std::string str;
++cursor;
char c1 = '\0';
for (; cursor < jsonSize; ++cursor) {
char c2 = json.at(cursor);
if (c1 != '\\' && c2 == '"') {
break;
}
str += c2;
c1 = c2;
}
data.emplace(Value::VT_STRING, str);
}
bool Parser::interpretValue(const std::string& value) {
std::string upperValue;
upperValue.reserve(value.size());
std::transform(value.begin(), value.end(), upperValue.begin(), toupper);
if (upperValue == "NULL") {
data.emplace(Value::VT_NULL, "");
} else if (upperValue == "TRUE") {
data.emplace(Value::VT_BOOL, "true");
} else if (upperValue == "FALSE") {
data.emplace(Value::VT_BOOL, "false");
} else {
bool number = std::all_of(value.begin(), value.end(), IsNumber);
if (!number) {
return false;
}
data.emplace(Value::VT_NUMBER, value);
}
return true;
}
} // namespace Jzon