You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
16 KiB
C++
559 lines
16 KiB
C++
18 years ago
|
// ***************************************************************** -*- C++ -*-
|
||
|
/*
|
||
|
* Copyright (C) 2007 Andreas Huggel <ahuggel@gmx.net>
|
||
|
*
|
||
|
* This program is part of the Exiv2 distribution.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; either version 2
|
||
|
* of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
||
|
*/
|
||
|
/*
|
||
|
File: xmp.cpp
|
||
|
Version: $Rev$
|
||
|
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
||
|
History: 13-July-07, ahu: created
|
||
|
*/
|
||
|
// *****************************************************************************
|
||
|
#include "rcsid.hpp"
|
||
|
EXIV2_RCSID("@(#) $Id$")
|
||
|
|
||
|
// *****************************************************************************
|
||
|
// included header files
|
||
|
#include "xmp.hpp"
|
||
|
#include "types.hpp"
|
||
|
#include "error.hpp"
|
||
|
#include "value.hpp"
|
||
|
#include "properties.hpp"
|
||
|
|
||
|
// + standard includes
|
||
|
#include <iostream>
|
||
|
#include <algorithm>
|
||
|
#include <cassert>
|
||
|
#include <string>
|
||
|
|
||
|
// Adobe XMP Toolkit
|
||
|
#ifdef EXV_HAVE_XMP_TOOLKIT
|
||
|
# define TXMP_STRING_TYPE std::string
|
||
|
# include <XMP.hpp>
|
||
|
# include <XMP.incl_cpp>
|
||
|
#endif // EXV_HAVE_XMP_TOOLKIT
|
||
|
|
||
|
// *****************************************************************************
|
||
|
namespace {
|
||
|
//! Unary predicate that matches an Xmpdatum by key
|
||
|
class FindXmpdatum {
|
||
|
public:
|
||
|
//! Constructor, initializes the object with key
|
||
|
FindXmpdatum(const Exiv2::XmpKey& key)
|
||
|
: key_(key.key()) {}
|
||
|
/*!
|
||
|
@brief Returns true if prefix and property of the argument
|
||
|
Xmpdatum are equal to that of the object.
|
||
|
*/
|
||
|
bool operator()(const Exiv2::Xmpdatum& xmpdatum) const
|
||
|
{ return key_ == xmpdatum.key(); }
|
||
|
|
||
|
private:
|
||
|
std::string key_;
|
||
|
|
||
|
}; // class FindXmpdatum
|
||
|
|
||
|
//! Make an XMP key from a schema namespace and property path
|
||
|
Exiv2::XmpKey::AutoPtr makeXmpKey(const std::string& schemaNs,
|
||
|
const std::string& propPath);
|
||
|
}
|
||
|
|
||
|
// *****************************************************************************
|
||
|
// class member definitions
|
||
|
namespace Exiv2 {
|
||
|
|
||
|
//! @cond IGNORE
|
||
|
|
||
|
//! Internal Pimpl structure with private members and data of class Xmpdatum.
|
||
|
struct Xmpdatum::Impl {
|
||
|
Impl(const XmpKey& key, const Value* pValue); //!< Constructor
|
||
|
Impl(const Impl& rhs); //!< Copy constructor
|
||
|
Impl& operator=(const Impl& rhs); //!< Assignment
|
||
|
|
||
|
// DATA
|
||
|
XmpKey::AutoPtr key_; //!< Key
|
||
|
Value::AutoPtr value_; //!< Value
|
||
|
};
|
||
|
//! @endcond
|
||
|
|
||
|
Xmpdatum::Impl::Impl(const XmpKey& key, const Value* pValue)
|
||
|
: key_(key.clone())
|
||
|
{
|
||
|
if (pValue) value_ = pValue->clone();
|
||
|
}
|
||
|
|
||
|
Xmpdatum::Impl::Impl(const Impl& rhs)
|
||
|
{
|
||
|
if (rhs.key_.get() != 0) key_ = rhs.key_->clone(); // deep copy
|
||
|
if (rhs.value_.get() != 0) value_ = rhs.value_->clone(); // deep copy
|
||
|
}
|
||
|
|
||
|
Xmpdatum::Impl::Impl& Xmpdatum::Impl::operator=(const Impl& rhs)
|
||
|
{
|
||
|
if (this == &rhs) return *this;
|
||
|
key_.reset();
|
||
|
if (rhs.key_.get() != 0) key_ = rhs.key_->clone(); // deep copy
|
||
|
value_.reset();
|
||
|
if (rhs.value_.get() != 0) value_ = rhs.value_->clone(); // deep copy
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
Xmpdatum::Xmpdatum(const XmpKey& key, const Value* pValue)
|
||
|
: p_(new Impl(key, pValue))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Xmpdatum::Xmpdatum(const Xmpdatum& rhs)
|
||
|
: Metadatum(rhs), p_(new Impl(*rhs.p_))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Xmpdatum& Xmpdatum::operator=(const Xmpdatum& rhs)
|
||
|
{
|
||
|
if (this == &rhs) return *this;
|
||
|
Metadatum::operator=(rhs);
|
||
|
*p_ = *rhs.p_;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
Xmpdatum::~Xmpdatum()
|
||
|
{
|
||
|
delete p_;
|
||
|
}
|
||
|
|
||
|
std::string Xmpdatum::key() const
|
||
|
{
|
||
|
return p_->key_.get() == 0 ? "" : p_->key_->key();
|
||
|
}
|
||
|
|
||
|
std::string Xmpdatum::groupName() const
|
||
|
{
|
||
|
return p_->key_.get() == 0 ? "" : p_->key_->groupName();
|
||
|
}
|
||
|
|
||
|
std::string Xmpdatum::tagName() const
|
||
|
{
|
||
|
return p_->key_.get() == 0 ? "" : p_->key_->tagName();
|
||
|
}
|
||
|
|
||
|
std::string Xmpdatum::tagLabel() const
|
||
|
{
|
||
|
return p_->key_.get() == 0 ? "" : p_->key_->tagLabel();
|
||
|
}
|
||
|
|
||
|
uint16_t Xmpdatum::tag() const
|
||
|
{
|
||
|
return p_->key_.get() == 0 ? 0 : p_->key_->tag();
|
||
|
}
|
||
|
|
||
|
TypeId Xmpdatum::typeId() const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? invalidTypeId : p_->value_->typeId();
|
||
|
}
|
||
|
|
||
|
const char* Xmpdatum::typeName() const
|
||
|
{
|
||
|
return TypeInfo::typeName(typeId());
|
||
|
}
|
||
|
|
||
|
long Xmpdatum::count() const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? 0 : p_->value_->count();
|
||
|
}
|
||
|
|
||
|
long Xmpdatum::size() const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? 0 : p_->value_->size();
|
||
|
}
|
||
|
|
||
|
std::string Xmpdatum::toString() const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? "" : p_->value_->toString();
|
||
|
}
|
||
|
|
||
|
std::string Xmpdatum::toString(long n) const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? "" : p_->value_->toString(n);
|
||
|
}
|
||
|
|
||
|
long Xmpdatum::toLong(long n) const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? -1 : p_->value_->toLong(n);
|
||
|
}
|
||
|
|
||
|
float Xmpdatum::toFloat(long n) const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? -1 : p_->value_->toFloat(n);
|
||
|
}
|
||
|
|
||
|
Rational Xmpdatum::toRational(long n) const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? Rational(-1, 1) : p_->value_->toRational(n);
|
||
|
}
|
||
|
|
||
|
Value::AutoPtr Xmpdatum::getValue() const
|
||
|
{
|
||
|
return p_->value_.get() == 0 ? Value::AutoPtr(0) : p_->value_->clone();
|
||
|
}
|
||
|
|
||
|
const Value& Xmpdatum::value() const
|
||
|
{
|
||
|
if (p_->value_.get() == 0) throw Error(8);
|
||
|
return *p_->value_;
|
||
|
}
|
||
|
|
||
|
long Xmpdatum::copy(byte* /*buf*/, ByteOrder /*byteOrder*/) const
|
||
|
{
|
||
|
throw Error(34, "Xmpdatum::copy");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
Xmpdatum& Xmpdatum::operator=(const uint16_t& value)
|
||
|
{
|
||
|
UShortValue::AutoPtr v(new UShortValue);
|
||
|
v->value_.push_back(value);
|
||
|
p_->value_ = v;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
Xmpdatum& Xmpdatum::operator=(const std::string& value)
|
||
|
{
|
||
|
setValue(value);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
Xmpdatum& Xmpdatum::operator=(const Value& value)
|
||
|
{
|
||
|
setValue(&value);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
void Xmpdatum::setValue(const Value* pValue)
|
||
|
{
|
||
|
p_->value_.reset();
|
||
|
if (pValue) p_->value_ = pValue->clone();
|
||
|
}
|
||
|
|
||
|
void Xmpdatum::setValue(const std::string& value)
|
||
|
{
|
||
|
// Todo: What's the correct default? Adjust doc
|
||
|
if (p_->value_.get() == 0) {
|
||
|
assert(0 != p_->key_.get());
|
||
|
TypeId type = XmpProperties::propertyType(*p_->key_.get());
|
||
|
p_->value_ = Value::create(type);
|
||
|
}
|
||
|
p_->value_->read(value);
|
||
|
}
|
||
|
|
||
|
Xmpdatum& XmpData::operator[](const std::string& key)
|
||
|
{
|
||
|
XmpKey xmpKey(key);
|
||
|
iterator pos = findKey(xmpKey);
|
||
|
if (pos == end()) {
|
||
|
add(Xmpdatum(xmpKey));
|
||
|
pos = findKey(xmpKey);
|
||
|
}
|
||
|
return *pos;
|
||
|
}
|
||
|
|
||
|
int XmpData::add(const XmpKey& key, Value* value)
|
||
|
{
|
||
|
return add(Xmpdatum(key, value));
|
||
|
}
|
||
|
|
||
|
int XmpData::add(const Xmpdatum& xmpDatum)
|
||
|
{
|
||
|
xmpMetadata_.push_back(xmpDatum);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
XmpData::const_iterator XmpData::findKey(const XmpKey& key) const
|
||
|
{
|
||
|
return std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(),
|
||
|
FindXmpdatum(key));
|
||
|
}
|
||
|
|
||
|
XmpData::iterator XmpData::findKey(const XmpKey& key)
|
||
|
{
|
||
|
return std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(),
|
||
|
FindXmpdatum(key));
|
||
|
}
|
||
|
|
||
|
void XmpData::clear()
|
||
|
{
|
||
|
xmpMetadata_.clear();
|
||
|
}
|
||
|
|
||
|
void XmpData::sortByKey()
|
||
|
{
|
||
|
std::sort(xmpMetadata_.begin(), xmpMetadata_.end(), cmpMetadataByKey);
|
||
|
}
|
||
|
|
||
|
XmpData::const_iterator XmpData::begin() const
|
||
|
{
|
||
|
return xmpMetadata_.begin();
|
||
|
}
|
||
|
|
||
|
XmpData::const_iterator XmpData::end() const
|
||
|
{
|
||
|
return xmpMetadata_.end();
|
||
|
}
|
||
|
|
||
|
bool XmpData::empty() const
|
||
|
{
|
||
|
return count() == 0;
|
||
|
}
|
||
|
|
||
|
long XmpData::count() const
|
||
|
{
|
||
|
return static_cast<long>(xmpMetadata_.size());
|
||
|
}
|
||
|
|
||
|
XmpData::iterator XmpData::begin()
|
||
|
{
|
||
|
return xmpMetadata_.begin();
|
||
|
}
|
||
|
|
||
|
XmpData::iterator XmpData::end()
|
||
|
{
|
||
|
return xmpMetadata_.end();
|
||
|
}
|
||
|
|
||
|
XmpData::iterator XmpData::erase(XmpData::iterator pos)
|
||
|
{
|
||
|
return xmpMetadata_.erase(pos);
|
||
|
}
|
||
|
|
||
|
bool XmpParser::initialized_ = false;
|
||
|
|
||
|
#ifdef EXV_HAVE_XMP_TOOLKIT
|
||
|
int XmpParser::decode( XmpData& xmpData,
|
||
|
const std::string& xmpPacket)
|
||
|
{ try {
|
||
|
xmpData.clear();
|
||
|
|
||
|
if (!initialized_) {
|
||
|
initialized_ = true;
|
||
|
if (!SXMPMeta::Initialize()) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "XMP Toolkit initialization failed.\n";
|
||
|
#endif
|
||
|
return 2;
|
||
|
}
|
||
|
}
|
||
|
SXMPMeta meta(xmpPacket.data(), xmpPacket.size());
|
||
|
SXMPIterator iter(meta);
|
||
|
std::string schemaNs, propPath, propValue;
|
||
|
XMP_OptionBits opt;
|
||
|
while (iter.Next(&schemaNs, &propPath, &propValue, &opt)) {
|
||
|
if (XMP_NodeIsSchema(opt)) continue;
|
||
|
|
||
|
XmpKey::AutoPtr key = makeXmpKey(schemaNs, propPath);
|
||
|
if (key.get() == 0) continue;
|
||
|
|
||
|
// Create an Exiv2 value and read the property value
|
||
|
Value::AutoPtr val = Value::create(XmpProperties::propertyType(*key.get()));
|
||
|
if (XMP_PropIsSimple(opt)) {
|
||
|
if (val->typeId() != xmpText) {
|
||
|
int ret = val->read(propValue);
|
||
|
if (ret != 0) val = Value::create(xmpText);
|
||
|
}
|
||
|
if (val->typeId() == xmpText) {
|
||
|
std::string pv = propValue;
|
||
|
// Todo: Do not use read() for XmpTextValues
|
||
|
quoteText(pv);
|
||
|
val->read(pv);
|
||
|
}
|
||
|
}
|
||
|
else if (XMP_PropIsArray(opt)) {
|
||
|
XMP_Index itemIdx = 1;
|
||
|
std::string itemValue, arrayValue;
|
||
|
XMP_OptionBits itemOpt;
|
||
|
while (meta.GetArrayItem(schemaNs.c_str(), propPath.c_str(),
|
||
|
itemIdx, &itemValue, &itemOpt)) {
|
||
|
if (val->typeId() == xmpText) quoteText(itemValue);
|
||
|
if (itemIdx > 1) arrayValue += " ";
|
||
|
arrayValue += itemValue;
|
||
|
++itemIdx;
|
||
|
}
|
||
|
iter.Skip(kXMP_IterSkipSubtree);
|
||
|
// Todo: Do not use read() for XmpTextValues
|
||
|
val->read(arrayValue);
|
||
|
}
|
||
|
else {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "Warning: XMP property " << key->key()
|
||
|
<< " has unsupported property type; skipping property.\n";
|
||
|
#endif
|
||
|
iter.Skip(kXMP_IterSkipSubtree);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
xmpData.add(*key.get(), val.get());
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
catch (const XMP_Error& e) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << Error(39, e.GetID(), e.GetErrMsg()) << "\n";
|
||
|
#endif
|
||
|
xmpData.clear();
|
||
|
return 3;
|
||
|
}} // XmpParser::decode
|
||
|
#else
|
||
|
int XmpParser::decode( XmpData& /*xmpData*/,
|
||
|
const std::string& xmpPacket)
|
||
|
{
|
||
|
if (!xmpPacket.empty()) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "Warning: XMP toolkit support not compiled in.\n";
|
||
|
#endif
|
||
|
}
|
||
|
return 1;
|
||
|
} // XmpParser::decode
|
||
|
#endif // !EXV_HAVE_XMP_TOOLKIT
|
||
|
|
||
|
#ifdef EXV_HAVE_XMP_TOOLKIT
|
||
|
int XmpParser::encode( std::string& xmpPacket,
|
||
|
const XmpData& xmpData)
|
||
|
{ try {
|
||
|
xmpPacket.clear();
|
||
|
|
||
|
if (!initialized_) {
|
||
|
initialized_ = true;
|
||
|
if (!SXMPMeta::Initialize()) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "XMP Toolkit initialization failed.\n";
|
||
|
#endif
|
||
|
return 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SXMPMeta meta;
|
||
|
|
||
|
for (XmpData::const_iterator i = xmpData.begin(); i != xmpData.end(); ++i) {
|
||
|
|
||
|
const char* ns = XmpProperties::ns(i->groupName());
|
||
|
|
||
|
// Todo: Make sure the namespace is registered with XMP-SDK
|
||
|
|
||
|
// Todo: Requires a separate indicator for array
|
||
|
// (or value type in general => reuse typeId?)
|
||
|
|
||
|
if (i->count() == 1) {
|
||
|
// simple property
|
||
|
//-ahu
|
||
|
std::cerr << i->key() << "\n";
|
||
|
meta.SetProperty(ns, i->tagName().c_str(), i->toString(0).c_str());
|
||
|
|
||
|
}
|
||
|
if (i->count() > 1) {
|
||
|
// array or sequence
|
||
|
for (int k = 0; k < i->count(); ++k) {
|
||
|
|
||
|
// Todo: Need indicator if array is ordered
|
||
|
|
||
|
//-ahu
|
||
|
std::cerr << i->key() << " count = " << i->count() << "\n";
|
||
|
|
||
|
meta.AppendArrayItem(ns, i->tagName().c_str(),
|
||
|
kXMP_PropArrayIsOrdered,
|
||
|
i->toString(k).c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
meta.SerializeToBuffer(&xmpPacket, kXMP_UseCompactFormat);
|
||
|
return 0;
|
||
|
}
|
||
|
catch (const XMP_Error& e) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << Error(39, e.GetID(), e.GetErrMsg()) << "\n";
|
||
|
#endif
|
||
|
xmpPacket.clear();
|
||
|
return 3;
|
||
|
}} // XmpParser::decode
|
||
|
#else
|
||
|
void XmpParser::encode( std::string& /*xmpPacket*/,
|
||
|
const XmpData& xmpData)
|
||
|
{
|
||
|
if (!xmpData.empty()) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "Warning: XMP toolkit support not compiled in.\n";
|
||
|
#endif
|
||
|
return 1;
|
||
|
}
|
||
|
} // XmpParser::encode
|
||
|
#endif // !EXV_HAVE_XMP_TOOLKIT
|
||
|
|
||
|
// *************************************************************************
|
||
|
// free functions
|
||
|
std::ostream& operator<<(std::ostream& os, const Xmpdatum& md)
|
||
|
{
|
||
|
return os << md.value();
|
||
|
}
|
||
|
|
||
|
} // namespace Exiv2
|
||
|
|
||
|
// *****************************************************************************
|
||
|
// local definitions
|
||
|
namespace {
|
||
|
Exiv2::XmpKey::AutoPtr makeXmpKey(const std::string& schemaNs,
|
||
|
const std::string& propPath)
|
||
|
{
|
||
|
std::string property;
|
||
|
std::string::size_type idx = propPath.find(':');
|
||
|
if (idx != std::string::npos) {
|
||
|
// Don't worry about out_of_range, XMP parser takes care of this
|
||
|
property = propPath.substr(idx + 1);
|
||
|
}
|
||
|
else {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "Warning: Failed to determine property name from path "
|
||
|
<< propPath << ", namespace " << schemaNs
|
||
|
<< "; skipping property.\n";
|
||
|
#endif
|
||
|
return Exiv2::XmpKey::AutoPtr();
|
||
|
}
|
||
|
const char* prefix = Exiv2::XmpProperties::prefix(schemaNs);
|
||
|
if (prefix == 0) {
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
// Todo: Print warning only for the first property in each ns
|
||
|
std::cerr << "Warning: Unknown schema namespace "
|
||
|
<< schemaNs << "; skipping property "
|
||
|
<< property << ".\n";
|
||
|
#endif
|
||
|
return Exiv2::XmpKey::AutoPtr();
|
||
|
}
|
||
|
Exiv2::XmpKey::AutoPtr key;
|
||
|
// Todo: Avoid the try/catch block
|
||
|
try {
|
||
|
key = Exiv2::XmpKey::AutoPtr(new Exiv2::XmpKey(prefix, property));
|
||
|
}
|
||
|
catch (const Exiv2::AnyError& e) {
|
||
|
// This should only happen for unknown property names
|
||
|
#ifndef SUPPRESS_WARNINGS
|
||
|
std::cerr << "Warning: " << e << "; skipping property.\n";
|
||
|
#endif
|
||
|
}
|
||
|
return key;
|
||
|
} // makeXmpKey
|
||
|
|
||
|
}
|