Remove exiv2json sample and tests depending on it

main
Luis Díaz Más 3 years ago
parent 3b9fcb4b3d
commit c6340caca7

@ -70,10 +70,7 @@ target_include_directories(path-test PRIVATE
${CMAKE_SOURCE_DIR}/app
) # To find getopt.hpp
add_executable( exiv2json exiv2json.cpp Jzon.cpp Jzon.h)
list(APPEND APPLICATIONS exiv2json)
install( TARGETS metacopy exiv2json RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install( TARGETS metacopy RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
if( EXPAT_FOUND )
add_executable( geotag geotag.cpp)

@ -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,280 +0,0 @@
# -*- coding: utf-8 -*-
import system_tests
class Exiv2jsonRecursiveJsonTreeWithXMP(metaclass=system_tests.CaseMeta):
url = "http://dev.exiv2.org/issues/1054"
env = {
'TZ': 'UTC'
}
filename1 = system_tests.path("$data_path/BlueSquare.xmp")
filename2 = system_tests.path("$data_path/exiv2-bug784.jpg")
commands = [ "$exiv2json $filename1",
"$exiv2json x $filename1",
"$exiv2json $filename2",
]
stdout = [ """{
"Exif": {
"Image": {
"ImageWidth": 360,
"ImageLength": 216,
"Orientation": 1,
"XResolution": [
720000,
10000
],
"YResolution": [
720000,
10000
],
"ResolutionUnit": 2,
"DateTime": "2005:09:07 22:09:51",
"ImageDescription": "XMPFiles BlueSquare test file, created in Photoshop CS2, saved as .psd, .jpg, and .tif.",
"Make": "Nikon"
},
"Photo": {
"ColorSpace": 1,
"PixelXDimension": 360,
"PixelYDimension": 216,
"DateTimeDigitized": "2005:09:07 22:07:40"
}
},
"Iptc": {
"Application2": {
"ObjectName": "Blue Square Test File - .jpg",
"Keywords": "XMP",
"Keywords": "Blue Square",
"Keywords": "test file",
"Keywords": "Photoshop",
"Keywords": ".jpg",
"DigitizationDate": "2005-09-07",
"Caption": "XMPFiles BlueSquare test file, created in Photoshop CS2, saved as .psd, .jpg, and .tif."
},
"Envelope": {
"CharacterSet": "%G"
}
},
"Xmp": {
"dc": {
"format": "image\/jpeg",
"title": {
"lang": {
"x-default": "Blue Square Test File - .jpg",
"en-US": "Blue Square Test File - .jpg",
"de-CH": "Blaues Quadrat Test Datei - .jpg"
}
},
"description": {
"lang": {
"x-default": "XMPFiles BlueSquare test file, created in Photoshop CS2, saved as .psd, .jpg, and .tif."
}
},
"subject": "XMP, Blue Square, test file, Photoshop, .jpg"
},
"xmp": {
"CreatorTool": "Adobe Photoshop CS2 Macintosh",
"CreateDate": "2005-09-07T15:07:40-07:00",
"ModifyDate": "2005-09-07T15:09:51-07:00",
"MetadataDate": "2006-04-10T13:37:10-07:00"
},
"xmpMM": {
"DocumentID": "uuid:9A3B7F52214211DAB6308A7391270C13",
"InstanceID": "uuid:B59AC1B3214311DAB6308A7391270C13",
"DerivedFrom": {
"stRef": {
"instanceID": "uuid:9A3B7F4F214211DAB6308A7391270C13",
"documentID": "uuid:9A3B7F4E214211DAB6308A7391270C13"
}
}
},
"photoshop": {
"ColorMode": "3",
"ICCProfile": "sRGB IEC61966-2.1"
},
"tiff": {
"Orientation": "1",
"XResolution": "720000\/10000",
"YResolution": "720000\/10000",
"ResolutionUnit": "2",
"ImageWidth": "360",
"ImageLength": "216",
"NativeDigest": "256,257,258,259,262,274,277,284,530,531,282,283,296,301,318,319,529,532,306,270,271,272,305,315,33432;D0485928256FC8D17D036C26919E106D",
"Make": "Nikon",
"BitsPerSample": "8, 8, 8"
},
"exif": {
"PixelXDimension": "360",
"PixelYDimension": "216",
"ColorSpace": "1",
"NativeDigest": "36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;76DBD9F0A5E7ED8F62B4CE8EFA6478B4"
},
"xmlns": {
"DerivedFrom": "",
"dc": "http:\/\/purl.org\/dc\/elements\/1.1\/",
"exif": "http:\/\/ns.adobe.com\/exif\/1.0\/",
"photoshop": "http:\/\/ns.adobe.com\/photoshop\/1.0\/",
"tiff": "http:\/\/ns.adobe.com\/tiff\/1.0\/",
"xmp": "http:\/\/ns.adobe.com\/xap\/1.0\/",
"xmpMM": "http:\/\/ns.adobe.com\/xap\/1.0\/mm\/"
}
}
}
""",
"""{
"Xmp": {
"dc": {
"format": "image\/jpeg",
"title": {
"lang": {
"x-default": "Blue Square Test File - .jpg",
"en-US": "Blue Square Test File - .jpg",
"de-CH": "Blaues Quadrat Test Datei - .jpg"
}
},
"description": {
"lang": {
"x-default": "XMPFiles BlueSquare test file, created in Photoshop CS2, saved as .psd, .jpg, and .tif."
}
},
"subject": "XMP, Blue Square, test file, Photoshop, .jpg"
},
"xmp": {
"CreatorTool": "Adobe Photoshop CS2 Macintosh",
"CreateDate": "2005-09-07T15:07:40-07:00",
"ModifyDate": "2005-09-07T15:09:51-07:00",
"MetadataDate": "2006-04-10T13:37:10-07:00"
},
"xmpMM": {
"DocumentID": "uuid:9A3B7F52214211DAB6308A7391270C13",
"InstanceID": "uuid:B59AC1B3214311DAB6308A7391270C13",
"DerivedFrom": {
"stRef": {
"instanceID": "uuid:9A3B7F4F214211DAB6308A7391270C13",
"documentID": "uuid:9A3B7F4E214211DAB6308A7391270C13"
}
}
},
"photoshop": {
"ColorMode": "3",
"ICCProfile": "sRGB IEC61966-2.1"
},
"tiff": {
"Orientation": "1",
"XResolution": "720000\/10000",
"YResolution": "720000\/10000",
"ResolutionUnit": "2",
"ImageWidth": "360",
"ImageLength": "216",
"NativeDigest": "256,257,258,259,262,274,277,284,530,531,282,283,296,301,318,319,529,532,306,270,271,272,305,315,33432;D0485928256FC8D17D036C26919E106D",
"Make": "Nikon",
"BitsPerSample": "8, 8, 8"
},
"exif": {
"PixelXDimension": "360",
"PixelYDimension": "216",
"ColorSpace": "1",
"NativeDigest": "36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;76DBD9F0A5E7ED8F62B4CE8EFA6478B4"
},
"xmlns": {
"DerivedFrom": "",
"dc": "http:\/\/purl.org\/dc\/elements\/1.1\/",
"exif": "http:\/\/ns.adobe.com\/exif\/1.0\/",
"photoshop": "http:\/\/ns.adobe.com\/photoshop\/1.0\/",
"tiff": "http:\/\/ns.adobe.com\/tiff\/1.0\/",
"xmp": "http:\/\/ns.adobe.com\/xap\/1.0\/",
"xmpMM": "http:\/\/ns.adobe.com\/xap\/1.0\/mm\/"
}
}
}
""",
"""{
"Exif": {
"Image": {
"Orientation": 1,
"XResolution": [
72,
1
],
"YResolution": [
72,
1
],
"ResolutionUnit": 2,
"YCbCrPositioning": 1,
"Copyright": "Public Domain. Do whatever you like with this image",
"ExifTag": 232
},
"Photo": {
"ExifVersion": "48 50 50 49"
}
},
"Iptc": {
"Envelope": {
"CharacterSet": "%G"
},
"Application2": {
"RecordVersion": 4,
"Keywords": "1st",
"Keywords": "2nd",
"Keywords": "next1",
"Keywords": "next2",
"Keywords": "root",
"Keywords": "root0",
"Copyright": "Public Domain. Do whatever you like with this image"
}
},
"Xmp": {
"dc": {
"format": "image\/jpeg",
"rights": {
"lang": {
"x-default": "Public Domain. Do whatever you like with this image"
}
},
"subject": "1st, 2nd, next1, next2, root, root0"
},
"xmpMM": {
"DocumentID": "004D48F936062EF5085A81BF96D4C494",
"OriginalDocumentID": "004D48F936062EF5085A81BF96D4C494",
"InstanceID": "xmp.iid:f74f0d02-e921-134e-8107-1dda17aad853",
"History": [
{
"stEvt": {
"action": "saved",
"instanceID": "xmp.iid:f74f0d02-e921-134e-8107-1dda17aad853",
"when": "2015-03-24T20:35:55-05:00",
"softwareAgent": "Adobe Photoshop Lightroom 4.4 (Windows)",
"changed": "\/metadata"
}
}
]
},
"xmp": {
"MetadataDate": "2015-03-24T20:35:55-05:00"
},
"crs": {
"RawFileName": "exiv2.lr.jpg"
},
"lr": {
"hierarchicalSubject": "root0|next1|next2, root|1st|2nd"
},
"xmlns": {
"crs": "http:\/\/ns.adobe.com\/camera-raw-settings\/1.0\/",
"dc": "http:\/\/purl.org\/dc\/elements\/1.1\/",
"lr": "http:\/\/ns.adobe.com\/lightroom\/1.0\/",
"stEvt": "http:\/\/ns.adobe.com\/xap\/1.0\/sType\/ResourceEvent#",
"xmp": "http:\/\/ns.adobe.com\/xap\/1.0\/",
"xmpMM": "http:\/\/ns.adobe.com\/xap\/1.0\/mm\/"
}
}
}
"""
]
stderr = [""] * len(commands)
retval = [0] * len(commands)

@ -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…
Cancel
Save