Remove exiv2json sample and tests depending on it
parent
3b9fcb4b3d
commit
c6340caca7
@ -1,974 +0,0 @@
|
||||
// ***************************************************************** -*- 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
|
@ -1,517 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
#ifndef Jzon_h__
|
||||
#define Jzon_h__
|
||||
|
||||
#ifndef JzonAPI
|
||||
#ifdef _WINDLL
|
||||
#define JzonAPI __declspec(dllimport)
|
||||
#elif defined(__GNUC__) && (__GNUC__ >= 4)
|
||||
#define JzonAPI __attribute__((visibility("default")))
|
||||
#else
|
||||
#define JzonAPI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <iterator>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Jzon {
|
||||
class Node;
|
||||
class Value;
|
||||
class Object;
|
||||
class Array;
|
||||
using NamedNode = std::pair<std::string, Node&>;
|
||||
using NamedNodePtr = std::pair<std::string, Node*>;
|
||||
|
||||
class TypeException : public std::logic_error {
|
||||
public:
|
||||
TypeException() : std::logic_error("A Node was used as the wrong type") {
|
||||
}
|
||||
};
|
||||
class NotFoundException : public std::out_of_range {
|
||||
public:
|
||||
NotFoundException() : std::out_of_range("The node could not be found") {
|
||||
}
|
||||
};
|
||||
|
||||
struct Format {
|
||||
bool newline;
|
||||
bool spacing;
|
||||
bool useTabs;
|
||||
unsigned int indentSize;
|
||||
};
|
||||
static const Format StandardFormat = {true, true, true, 1};
|
||||
static const Format NoFormat = {false, false, false, 0};
|
||||
|
||||
class JzonAPI Node {
|
||||
friend class Object;
|
||||
friend class Array;
|
||||
|
||||
public:
|
||||
enum Type { T_OBJECT, T_ARRAY, T_VALUE };
|
||||
|
||||
Node() noexcept = default;
|
||||
virtual ~Node() noexcept = default;
|
||||
|
||||
virtual Type GetType() const = 0;
|
||||
|
||||
inline bool IsObject() const {
|
||||
return (GetType() == T_OBJECT);
|
||||
}
|
||||
inline bool IsArray() const {
|
||||
return (GetType() == T_ARRAY);
|
||||
}
|
||||
inline bool IsValue() const {
|
||||
return (GetType() == T_VALUE);
|
||||
}
|
||||
|
||||
Object& AsObject();
|
||||
const Object& AsObject() const;
|
||||
Array& AsArray();
|
||||
const Array& AsArray() const;
|
||||
Value& AsValue();
|
||||
const Value& AsValue() const;
|
||||
|
||||
virtual inline bool IsNull() const {
|
||||
return false;
|
||||
}
|
||||
virtual inline bool IsString() const {
|
||||
return false;
|
||||
}
|
||||
virtual inline bool IsNumber() const {
|
||||
return false;
|
||||
}
|
||||
virtual inline bool IsBool() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual std::string ToString() const {
|
||||
throw TypeException();
|
||||
}
|
||||
virtual int ToInt() const {
|
||||
throw TypeException();
|
||||
}
|
||||
virtual float ToFloat() const {
|
||||
throw TypeException();
|
||||
}
|
||||
virtual double ToDouble() const {
|
||||
throw TypeException();
|
||||
}
|
||||
virtual bool ToBool() const {
|
||||
throw TypeException();
|
||||
}
|
||||
|
||||
virtual bool Has(const std::string& /*name*/) const {
|
||||
throw TypeException();
|
||||
}
|
||||
virtual size_t GetCount() const {
|
||||
return 0;
|
||||
}
|
||||
virtual Node& Get(const std::string& /*name*/) const {
|
||||
throw TypeException();
|
||||
}
|
||||
virtual Node& Get(size_t /*index*/) const {
|
||||
throw TypeException();
|
||||
}
|
||||
|
||||
static Type DetermineType(const std::string& json);
|
||||
|
||||
protected:
|
||||
virtual Node* GetCopy() const = 0;
|
||||
};
|
||||
|
||||
class JzonAPI Value : public Node {
|
||||
public:
|
||||
enum ValueType { VT_NULL, VT_STRING, VT_NUMBER, VT_BOOL };
|
||||
|
||||
Value();
|
||||
Value(const Value& rhs);
|
||||
Value(const Node& rhs);
|
||||
Value(ValueType type, const std::string& value);
|
||||
Value(const std::string& value);
|
||||
Value(const char* value);
|
||||
Value(const int value);
|
||||
Value(const float value);
|
||||
Value(const double value);
|
||||
Value(const bool value);
|
||||
~Value() override = default;
|
||||
|
||||
Type GetType() const override;
|
||||
ValueType GetValueType() const;
|
||||
|
||||
inline bool IsNull() const override {
|
||||
return (type == VT_NULL);
|
||||
}
|
||||
inline bool IsString() const override {
|
||||
return (type == VT_STRING);
|
||||
}
|
||||
inline bool IsNumber() const override {
|
||||
return (type == VT_NUMBER);
|
||||
}
|
||||
inline bool IsBool() const override {
|
||||
return (type == VT_BOOL);
|
||||
}
|
||||
|
||||
std::string ToString() const override;
|
||||
int ToInt() const override;
|
||||
float ToFloat() const override;
|
||||
double ToDouble() const override;
|
||||
bool ToBool() const override;
|
||||
|
||||
void SetNull();
|
||||
void Set(const Value& value);
|
||||
void Set(ValueType type, const std::string& value);
|
||||
void Set(const std::string& value);
|
||||
void Set(const char* value);
|
||||
void Set(const int value);
|
||||
void Set(const float value);
|
||||
void Set(const double value);
|
||||
void Set(const bool value);
|
||||
|
||||
Value& operator=(const Value& rhs);
|
||||
Value& operator=(const Node& rhs);
|
||||
Value& operator=(const std::string& rhs);
|
||||
Value& operator=(const char* rhs);
|
||||
Value& operator=(const int rhs);
|
||||
Value& operator=(const float rhs);
|
||||
Value& operator=(const double rhs);
|
||||
Value& operator=(const bool rhs);
|
||||
|
||||
bool operator==(const Value& other) const;
|
||||
bool operator!=(const Value& other) const;
|
||||
|
||||
static std::string EscapeString(const std::string& value);
|
||||
static std::string UnescapeString(const std::string& value);
|
||||
|
||||
protected:
|
||||
Node* GetCopy() const override;
|
||||
|
||||
private:
|
||||
std::string valueStr;
|
||||
ValueType type;
|
||||
};
|
||||
|
||||
static const Value null;
|
||||
|
||||
class JzonAPI Object : public Node {
|
||||
public:
|
||||
class iterator : public std::iterator<std::input_iterator_tag, NamedNode> {
|
||||
public:
|
||||
iterator(NamedNodePtr* o) : p(o) {
|
||||
}
|
||||
iterator(const iterator& it) : p(it.p) {
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
++p;
|
||||
return *this;
|
||||
}
|
||||
iterator operator++(int) {
|
||||
iterator tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& rhs) {
|
||||
return p == rhs.p;
|
||||
}
|
||||
bool operator!=(const iterator& rhs) {
|
||||
return p != rhs.p;
|
||||
}
|
||||
|
||||
NamedNode operator*() {
|
||||
return NamedNode(p->first, *p->second);
|
||||
}
|
||||
|
||||
private:
|
||||
NamedNodePtr* p;
|
||||
};
|
||||
class const_iterator : public std::iterator<std::input_iterator_tag, const NamedNode> {
|
||||
public:
|
||||
const_iterator(const NamedNodePtr* o) : p(o) {
|
||||
}
|
||||
const_iterator(const const_iterator& it) : p(it.p) {
|
||||
}
|
||||
|
||||
const_iterator& operator++() {
|
||||
++p;
|
||||
return *this;
|
||||
}
|
||||
const_iterator operator++(int) {
|
||||
const_iterator tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& rhs) {
|
||||
return p == rhs.p;
|
||||
}
|
||||
bool operator!=(const const_iterator& rhs) {
|
||||
return p != rhs.p;
|
||||
}
|
||||
|
||||
const NamedNode operator*() {
|
||||
return NamedNode(p->first, *p->second);
|
||||
}
|
||||
|
||||
private:
|
||||
const NamedNodePtr* p;
|
||||
};
|
||||
|
||||
Object() = default;
|
||||
Object(const Object& other);
|
||||
Object(const Node& other);
|
||||
~Object() override;
|
||||
|
||||
Type GetType() const override;
|
||||
|
||||
void Add(const std::string& name, Node& node);
|
||||
void Add(const std::string& name, const Value& node);
|
||||
void Remove(const std::string& name);
|
||||
void Clear();
|
||||
|
||||
iterator begin();
|
||||
const_iterator begin() const;
|
||||
iterator end();
|
||||
const_iterator end() const;
|
||||
|
||||
bool Has(const std::string& name) const override;
|
||||
size_t GetCount() const override;
|
||||
Node& Get(const std::string& name) const override;
|
||||
using Node::Get;
|
||||
|
||||
protected:
|
||||
Node* GetCopy() const override;
|
||||
|
||||
private:
|
||||
using ChildList = std::vector<NamedNodePtr>;
|
||||
ChildList children;
|
||||
};
|
||||
|
||||
class JzonAPI Array : public Node {
|
||||
public:
|
||||
class iterator : public std::iterator<std::input_iterator_tag, Node> {
|
||||
public:
|
||||
iterator(Node** o) : p(o) {
|
||||
}
|
||||
iterator(const iterator& it) : p(it.p) {
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
++p;
|
||||
return *this;
|
||||
}
|
||||
iterator operator++(int) {
|
||||
iterator tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& rhs) {
|
||||
return p == rhs.p;
|
||||
}
|
||||
bool operator!=(const iterator& rhs) {
|
||||
return p != rhs.p;
|
||||
}
|
||||
|
||||
Node& operator*() {
|
||||
return **p;
|
||||
}
|
||||
|
||||
private:
|
||||
Node** p;
|
||||
};
|
||||
class const_iterator : public std::iterator<std::input_iterator_tag, const Node> {
|
||||
public:
|
||||
const_iterator(const Node* const* o) : p(o) {
|
||||
}
|
||||
const_iterator(const const_iterator& it) : p(it.p) {
|
||||
}
|
||||
|
||||
const_iterator& operator++() {
|
||||
++p;
|
||||
return *this;
|
||||
}
|
||||
const_iterator operator++(int) {
|
||||
const_iterator tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& rhs) {
|
||||
return p == rhs.p;
|
||||
}
|
||||
bool operator!=(const const_iterator& rhs) {
|
||||
return p != rhs.p;
|
||||
}
|
||||
|
||||
const Node& operator*() {
|
||||
return **p;
|
||||
}
|
||||
|
||||
private:
|
||||
const Node* const* p;
|
||||
};
|
||||
|
||||
Array() = default;
|
||||
Array(const Array& other);
|
||||
Array(const Node& other);
|
||||
~Array() override;
|
||||
|
||||
Type GetType() const override;
|
||||
|
||||
void Add(Node& node);
|
||||
void Add(const Value& node);
|
||||
void Remove(size_t index);
|
||||
void Clear();
|
||||
|
||||
iterator begin();
|
||||
const_iterator begin() const;
|
||||
iterator end();
|
||||
const_iterator end() const;
|
||||
|
||||
size_t GetCount() const override;
|
||||
Node& Get(size_t index) const override;
|
||||
using Node::Get;
|
||||
|
||||
protected:
|
||||
Node* GetCopy() const override;
|
||||
|
||||
private:
|
||||
using ChildList = std::vector<Node*>;
|
||||
ChildList children;
|
||||
};
|
||||
|
||||
class JzonAPI FileWriter {
|
||||
public:
|
||||
FileWriter(std::string filename);
|
||||
~FileWriter() = default;
|
||||
|
||||
static void WriteFile(const std::string& filename, const Node& root, const Format& format = NoFormat);
|
||||
|
||||
void Write(const Node& root, const Format& format = NoFormat);
|
||||
|
||||
private:
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
class JzonAPI FileReader {
|
||||
public:
|
||||
FileReader(const std::string& filename);
|
||||
~FileReader() = default;
|
||||
|
||||
static bool ReadFile(const std::string& filename, Node& node);
|
||||
|
||||
bool Read(Node& node);
|
||||
|
||||
Node::Type DetermineType();
|
||||
|
||||
const std::string& GetError() const;
|
||||
|
||||
private:
|
||||
static bool loadFile(const std::string& filename, std::string& json);
|
||||
std::string json;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
class JzonAPI Writer {
|
||||
public:
|
||||
Writer(const Node& root, const Format& format = NoFormat);
|
||||
~Writer();
|
||||
|
||||
void SetFormat(const Format& format);
|
||||
const std::string& Write();
|
||||
|
||||
// Return result from last call to Write()
|
||||
const std::string& GetResult() const;
|
||||
|
||||
// Disable assignment operator
|
||||
Writer& operator=(const Writer&) = delete;
|
||||
|
||||
private:
|
||||
void writeNode(const Node& node, size_t level);
|
||||
void writeObject(const Object& node, size_t level);
|
||||
void writeArray(const Array& node, size_t level);
|
||||
void writeValue(const Value& node);
|
||||
|
||||
std::string result;
|
||||
|
||||
class FormatInterpreter* fi;
|
||||
|
||||
const Node& root;
|
||||
};
|
||||
|
||||
class JzonAPI Parser {
|
||||
public:
|
||||
Parser(Node& root);
|
||||
Parser(Node& root, const std::string& json);
|
||||
~Parser() = default;
|
||||
|
||||
void SetJson(const std::string& json);
|
||||
bool Parse();
|
||||
|
||||
const std::string& GetError() const;
|
||||
|
||||
// Disable assignment operator
|
||||
Parser& operator=(const Parser&) = delete;
|
||||
|
||||
private:
|
||||
enum Token {
|
||||
T_UNKNOWN,
|
||||
T_OBJ_BEGIN,
|
||||
T_OBJ_END,
|
||||
T_ARRAY_BEGIN,
|
||||
T_ARRAY_END,
|
||||
T_SEPARATOR_NODE,
|
||||
T_SEPARATOR_NAME,
|
||||
T_VALUE
|
||||
};
|
||||
|
||||
void tokenize();
|
||||
bool assemble();
|
||||
|
||||
char peek();
|
||||
void jumpToNext(char c);
|
||||
void jumpToCommentEnd();
|
||||
|
||||
void readString();
|
||||
bool interpretValue(const std::string& value);
|
||||
|
||||
std::string json;
|
||||
std::size_t jsonSize{0};
|
||||
|
||||
std::queue<Token> tokens;
|
||||
std::queue<std::pair<Value::ValueType, std::string>> data;
|
||||
|
||||
std::size_t cursor{0};
|
||||
|
||||
Node& root;
|
||||
|
||||
std::string error;
|
||||
};
|
||||
} // namespace Jzon
|
||||
|
||||
#endif // Jzon_h__
|
@ -1,330 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Sample program to print metadata in JSON format
|
||||
|
||||
#include <exiv2/exiv2.hpp>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
#include "Jzon.h"
|
||||
|
||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||
#ifndef __MINGW__
|
||||
#define __MINGW__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct Token {
|
||||
std::string n; // the name eg "History"
|
||||
bool a; // name is an array eg History[]
|
||||
int i; // index (indexed from 1) eg History[1]/stEvt:action
|
||||
};
|
||||
using Tokens = std::vector<Token>;
|
||||
|
||||
// "XMP.xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle"
|
||||
bool getToken(std::string& in, Token& token, std::set<std::string>* pNS = nullptr) {
|
||||
bool result = false;
|
||||
bool ns = false;
|
||||
|
||||
token.n = "";
|
||||
token.a = false;
|
||||
token.i = 0;
|
||||
|
||||
while (!result && in.length()) {
|
||||
std::string c = in.substr(0, 1);
|
||||
char C = c.at(0);
|
||||
in = in.substr(1, std::string::npos);
|
||||
if (in.length() == 0 && C != ']')
|
||||
token.n += c;
|
||||
if (C == '/' || C == '[' || C == ':' || C == '.' || C == ']' || in.length() == 0) {
|
||||
ns |= C == '/';
|
||||
token.a = C == '[';
|
||||
if (C == ']')
|
||||
token.i = std::atoi(token.n.c_str()); // encoded string first index == 1
|
||||
result = token.n.length() > 0;
|
||||
} else {
|
||||
token.n += c;
|
||||
}
|
||||
}
|
||||
if (ns && pNS)
|
||||
pNS->insert(token.n);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Jzon::Node& addToTree(Jzon::Node& r1, const Token& token) {
|
||||
Jzon::Object object;
|
||||
Jzon::Array array;
|
||||
|
||||
std::string key = token.n;
|
||||
size_t index = token.i - 1; // array Eg: "History[1]" indexed from 1. Jzon expects 0 based index.
|
||||
auto& empty = token.a ? static_cast<Jzon::Node&>(array) : static_cast<Jzon::Node&>(object);
|
||||
|
||||
if (r1.IsObject()) {
|
||||
Jzon::Object& o1 = r1.AsObject();
|
||||
if (!o1.Has(key))
|
||||
o1.Add(key, empty);
|
||||
return o1.Get(key);
|
||||
}
|
||||
if (r1.IsArray()) {
|
||||
Jzon::Array& a1 = r1.AsArray();
|
||||
while (a1.GetCount() <= index)
|
||||
a1.Add(empty);
|
||||
return a1.Get(index);
|
||||
}
|
||||
return r1;
|
||||
}
|
||||
|
||||
Jzon::Node& recursivelyBuildTree(Jzon::Node& root, Tokens& tokens, size_t k) {
|
||||
return addToTree(k == 0 ? root : recursivelyBuildTree(root, tokens, k - 1), tokens.at(k));
|
||||
}
|
||||
|
||||
// build the json tree for this key. return location and discover the name
|
||||
Jzon::Node& objectForKey(const std::string& Key, Jzon::Object& root, std::string& name,
|
||||
std::set<std::string>* pNS = nullptr) {
|
||||
// Parse the key
|
||||
Tokens tokens;
|
||||
Token token;
|
||||
std::string input = Key; // Example: "XMP.xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle"
|
||||
while (getToken(input, token, pNS))
|
||||
tokens.push_back(token);
|
||||
size_t l = tokens.size() - 1; // leave leaf name to push()
|
||||
name = tokens.at(l).n;
|
||||
|
||||
// The second token. For example: XMP.dc is a namespace
|
||||
if (pNS && tokens.size() > 1)
|
||||
pNS->insert(tokens[1].n);
|
||||
return recursivelyBuildTree(root, tokens, l - 1);
|
||||
|
||||
#if 0
|
||||
// recursivelyBuildTree:
|
||||
// Go to the root. Climb out adding objects or arrays to create the tree
|
||||
// The leaf is pushed on the top by the caller of objectForKey()
|
||||
// The recursion could be expressed by these if statements:
|
||||
if ( l == 1 ) return addToTree(root,tokens[0]);
|
||||
if ( l == 2 ) return addToTree(addToTree(root,tokens[0]),tokens[1]);
|
||||
if ( l == 3 ) return addToTree(addToTree(addToTree(root,tokens[0]),tokens[1]),tokens[2]);
|
||||
if ( l == 4 ) return addToTree(addToTree(addToTree(addToTree(root,tokens[0]),tokens[1]),tokens[2]),tokens[3]);
|
||||
...
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isObject(std::string& value) {
|
||||
return value == std::string("type=\"Struct\"");
|
||||
}
|
||||
|
||||
bool isArray(std::string& value) {
|
||||
return value == "type=\"Seq\"" || value == "type=\"Bag\"" || value == "type=\"Alt\"";
|
||||
}
|
||||
|
||||
#define STORE(node, key, value) \
|
||||
if (node.IsObject()) \
|
||||
node.AsObject().Add(key, value); \
|
||||
else \
|
||||
node.AsArray().Add(value)
|
||||
|
||||
template <class T>
|
||||
void push(Jzon::Node& node, const std::string& key, T i) {
|
||||
#define ABORT_IF_I_EMPTY \
|
||||
if (i->value().size() == 0) { \
|
||||
return; \
|
||||
}
|
||||
|
||||
std::string value = i->value().toString();
|
||||
|
||||
switch (i->typeId()) {
|
||||
case Exiv2::xmpText:
|
||||
if (::isObject(value)) {
|
||||
Jzon::Object v;
|
||||
STORE(node, key, v);
|
||||
} else if (::isArray(value)) {
|
||||
Jzon::Array v;
|
||||
STORE(node, key, v);
|
||||
} else {
|
||||
STORE(node, key, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Exiv2::unsignedByte:
|
||||
case Exiv2::unsignedShort:
|
||||
case Exiv2::unsignedLong:
|
||||
case Exiv2::signedByte:
|
||||
case Exiv2::signedShort:
|
||||
case Exiv2::signedLong:
|
||||
STORE(node, key, std::atoi(value.c_str()));
|
||||
break;
|
||||
|
||||
case Exiv2::tiffFloat:
|
||||
case Exiv2::tiffDouble:
|
||||
STORE(node, key, std::atof(value.c_str()));
|
||||
break;
|
||||
|
||||
case Exiv2::unsignedRational:
|
||||
case Exiv2::signedRational: {
|
||||
ABORT_IF_I_EMPTY
|
||||
Jzon::Array arr;
|
||||
Exiv2::Rational rat = i->value().toRational();
|
||||
arr.Add(rat.first);
|
||||
arr.Add(rat.second);
|
||||
STORE(node, key, arr);
|
||||
} break;
|
||||
|
||||
case Exiv2::langAlt: {
|
||||
ABORT_IF_I_EMPTY
|
||||
Jzon::Object l;
|
||||
const auto& langs = dynamic_cast<const Exiv2::LangAltValue&>(i->value());
|
||||
for (auto&& lang : langs.value_) {
|
||||
l.Add(lang.first, lang.second);
|
||||
}
|
||||
Jzon::Object o;
|
||||
o.Add("lang", l);
|
||||
STORE(node, key, o);
|
||||
} break;
|
||||
|
||||
default:
|
||||
case Exiv2::date:
|
||||
case Exiv2::time:
|
||||
case Exiv2::asciiString:
|
||||
case Exiv2::string:
|
||||
case Exiv2::comment:
|
||||
case Exiv2::undefined:
|
||||
case Exiv2::tiffIfd:
|
||||
case Exiv2::directory:
|
||||
case Exiv2::xmpAlt:
|
||||
case Exiv2::xmpBag:
|
||||
case Exiv2::xmpSeq:
|
||||
// http://dev.exiv2.org/boards/3/topics/1367#message-1373
|
||||
if (key == "UserComment") {
|
||||
size_t pos = value.find('\0');
|
||||
if (pos != std::string::npos)
|
||||
value = value.substr(0, pos);
|
||||
}
|
||||
if (key == "MakerNote")
|
||||
return;
|
||||
STORE(node, key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void fileSystemPush(const char* path, Jzon::Node& nfs) {
|
||||
auto& fs = dynamic_cast<Jzon::Object&>(nfs);
|
||||
fs.Add("path", path);
|
||||
fs.Add("realpath", std::filesystem::absolute(std::filesystem::path(path)).string());
|
||||
|
||||
struct stat buf = {};
|
||||
stat(path, &buf);
|
||||
|
||||
fs.Add("st_dev", static_cast<int>(buf.st_dev)); /* ID of device containing file */
|
||||
fs.Add("st_ino", static_cast<int>(buf.st_ino)); /* inode number */
|
||||
fs.Add("st_mode", static_cast<int>(buf.st_mode)); /* protection */
|
||||
fs.Add("st_nlink", static_cast<int>(buf.st_nlink)); /* number of hard links */
|
||||
fs.Add("st_uid", static_cast<int>(buf.st_uid)); /* user ID of owner */
|
||||
fs.Add("st_gid", static_cast<int>(buf.st_gid)); /* group ID of owner */
|
||||
fs.Add("st_rdev", static_cast<int>(buf.st_rdev)); /* device ID (if special file) */
|
||||
fs.Add("st_size", static_cast<int>(buf.st_size)); /* total size, in bytes */
|
||||
fs.Add("st_atime", static_cast<int>(buf.st_atime)); /* time of last access */
|
||||
fs.Add("st_mtime", static_cast<int>(buf.st_mtime)); /* time of last modification */
|
||||
fs.Add("st_ctime", static_cast<int>(buf.st_ctime)); /* time of last status change */
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW__)
|
||||
size_t blksize = 1024;
|
||||
size_t blocks = (buf.st_size + blksize - 1) / blksize;
|
||||
#else
|
||||
size_t blksize = buf.st_blksize;
|
||||
size_t blocks = buf.st_blocks;
|
||||
#endif
|
||||
fs.Add("st_blksize", static_cast<int>(blksize)); /* blocksize for file system I/O */
|
||||
fs.Add("st_blocks", static_cast<int>(blocks)); /* number of 512B blocks allocated */
|
||||
}
|
||||
|
||||
int main(int argc, char* const argv[]) {
|
||||
Exiv2::XmpParser::initialize();
|
||||
::atexit(Exiv2::XmpParser::terminate);
|
||||
#ifdef EXV_ENABLE_BMFF
|
||||
Exiv2::enableBMFF();
|
||||
#endif
|
||||
|
||||
try {
|
||||
if (argc < 2 || argc > 3) {
|
||||
std::cout << "Usage: " << argv[0] << " [-option] file" << std::endl;
|
||||
std::cout << "Option: all | exif | iptc | xmp | filesystem" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const char* path = argv[argc - 1];
|
||||
const char* opt = argc == 3 ? argv[1] : "-all";
|
||||
while (opt[0] == '-')
|
||||
opt++; // skip past leading -'s
|
||||
char option = opt[0];
|
||||
|
||||
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open(path);
|
||||
image->readMetadata();
|
||||
|
||||
Jzon::Object root;
|
||||
|
||||
if (option == 'f') { // only report filesystem when requested
|
||||
const char* Fs = "FS";
|
||||
Jzon::Object fs;
|
||||
root.Add(Fs, fs);
|
||||
fileSystemPush(path, root.Get(Fs));
|
||||
}
|
||||
|
||||
if (option == 'a' || option == 'e') {
|
||||
Exiv2::ExifData& exifData = image->exifData();
|
||||
for (auto i = exifData.begin(); i != exifData.end(); ++i) {
|
||||
std::string name;
|
||||
Jzon::Node& object = objectForKey(i->key(), root, name);
|
||||
push(object, name, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (option == 'a' || option == 'i') {
|
||||
Exiv2::IptcData& iptcData = image->iptcData();
|
||||
for (auto i = iptcData.begin(); i != iptcData.end(); ++i) {
|
||||
std::string name;
|
||||
Jzon::Node& object = objectForKey(i->key(), root, name);
|
||||
push(object, name, i);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EXV_HAVE_XMP_TOOLKIT
|
||||
if (option == 'a' || option == 'x') {
|
||||
Exiv2::XmpData& xmpData = image->xmpData();
|
||||
if (!xmpData.empty()) {
|
||||
// get the xmpData and recursively parse into a Jzon Object
|
||||
std::set<std::string> namespaces;
|
||||
for (auto i = xmpData.begin(); i != xmpData.end(); ++i) {
|
||||
std::string name;
|
||||
Jzon::Node& object = objectForKey(i->key(), root, name, &namespaces);
|
||||
push(object, name, i);
|
||||
}
|
||||
|
||||
// get the namespace dictionary from XMP
|
||||
Exiv2::Dictionary nsDict;
|
||||
Exiv2::XmpProperties::registeredNamespaces(nsDict);
|
||||
|
||||
// create and populate a Jzon::Object for the namespaces
|
||||
Jzon::Object xmlns;
|
||||
for (auto&& ns : namespaces) {
|
||||
xmlns.Add(ns, nsDict[ns]);
|
||||
}
|
||||
|
||||
// add xmlns as Xmp.xmlns
|
||||
root.Get("Xmp").AsObject().Add("xmlns", xmlns);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Jzon::Writer writer(root, Jzon::StandardFormat);
|
||||
writer.Write();
|
||||
std::cout << writer.GetResult() << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
catch (Exiv2::Error& e) {
|
||||
std::cout << "Caught Exiv2 exception '" << e.what() << "'\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import system_tests
|
||||
|
||||
|
||||
class XMPMetaDeleteNamespace(
|
||||
metaclass=system_tests.CaseMeta):
|
||||
"""
|
||||
Regression test for the bug described in:
|
||||
https://github.com/Exiv2/exiv2/issues/984
|
||||
"""
|
||||
url = "https://github.com/Exiv2/exiv2/issues/984"
|
||||
|
||||
filename = system_tests.path(
|
||||
"$data_path/issue_94_poc3.pgf"
|
||||
)
|
||||
commands = ["$exiv2json $filename"]
|
||||
stdout = ["""Caught Exiv2 exception 'XMP Toolkit error 9: Fatal namespace map problem'
|
||||
"""]
|
||||
stderr = ["""Warning: Removing 54 characters from the beginning of the XMP packet
|
||||
"""]
|
||||
retval = [1]
|
Loading…
Reference in New Issue