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