From bc2fa9a4dedb0a9da4dd2ef2e7281a4cfb33e34f Mon Sep 17 00:00:00 2001 From: Andreas Huggel Date: Sun, 16 Sep 2007 10:30:21 +0000 Subject: [PATCH] Merged rev. 1198-1213 from branches/xmp. --- README-XMP | 52 +++--- src/error.cpp | 13 +- src/exiv2.cpp | 2 + src/jpgimage.cpp | 42 +++-- src/pngchunk.cpp | 2 +- src/properties.cpp | 182 +++++++++++++++---- src/properties.hpp | 31 +++- src/types.cpp | 4 +- src/types.hpp | 2 +- src/value.cpp | 389 ++++++++++++++++++++++++++-------------- src/value.hpp | 398 +++++++++++++++++++++++++++++------------ src/xmp.cpp | 390 +++++++++++++++++++++++++++++----------- src/xmp.hpp | 17 ++ src/xmpparse.cpp | 1 + src/xmpparser-test.cpp | 2 +- 15 files changed, 1089 insertions(+), 438 deletions(-) diff --git a/README-XMP b/README-XMP index 9c0d237e..1ca7bd7d 100644 --- a/README-XMP +++ b/README-XMP @@ -1,39 +1,43 @@ XMP support =========== -Exiv2 uses the Adobe XMP toolkit (XMP SDK) to parse -and serialize XMP packets (only the XMPCore component). +Top-level Exiv2 classes to access XMP metadata are XmpData, +Xmpdatum and XmpKey. They work similar to the corresponding +Exif and IPTC classes. The property-repository is XmpProperties. +In addition to the expected new members, class Image also +has a new interface to access the raw XMP packet. Todo: XMP support is controlled with the ENABLE_XMP directive in config/config.mk.in. This will be a configure option eventually. -Top-level classes to access XMP metadata are XmpData, -XmpDatum and XmpKey. They work similar to the corresponding -Exif and IPTC classes. The property-repository is XmpProperties. -In addition to the expected new members, class Image also -has a new interface to access the raw XMP packet. - -Supported XMP types -------------------- -Simple types : supported -Structures : not supported -Arrays : unordered and ordered arrays are supported - alternative arrays are not supported +Exiv2 uses the Adobe XMP toolkit (XMP SDK) to parse +and serialize XMP packets (only the XMPCore component). -Property Qualifiers : not supported -Language Alternatives : not supported +Supported XMP value types +------------------------- +All XMP value types are supported: Simple types, structures, +arrays, property qualifiers and language alternatives. XMP properties are accessed through keys of the form -"Xmp..", where is the preferred -(or rather, registered) prefix for a schema namespace and - is the name of a property in that namespace. Only -known properties in known namespaces are supported at the -moment. Functions to register namespaces and properterties -are not provided yet. +"Xmp..", where is the preferred +(or rather, registered) prefix for a schema namespace and + is the path of the XMP node. In its most basic +form, to address simple properties, is the name +of the property. In general, can be used to +address any XMP node, including array items, structure fields +qualifiers and deeply nested properties. + +Any properties in known namespaces are supported and additional +namespaces can be registered. + +The specialized Exiv2 values XmpArrayValue and LangAltValue are +provided to simplify the use of XMP properties. Note: Unlike Exif and IPTC tags, XMP properties do not have a tag number. +Todo: Conversion between XMP and Exif/IPTC metadata. + XMP toolkit installation ======================== This is what worked for me on a Debian GNU/Linux (testing) @@ -52,8 +56,8 @@ this via configure options. External packages (non-Debian) ----------------- -xmp_v411_sdk.zip - from adobe.com: http://www.adobe.com/devnet/xmp/sdk/eula.html -expat-2.0.1.tar.gz - from sourceforge.net: http://sourceforge.net/project/showfiles.php?group_id=10127 +xmp_v411_sdk.zip - http://www.adobe.com/devnet/xmp/sdk/eula.html +expat-2.0.1.tar.gz - http://sourceforge.net/project/showfiles.php?group_id=10127 exiv2 - from SVN Installation steps diff --git a/src/error.cpp b/src/error.cpp index 841bcd26..a6b701cc 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -77,11 +77,20 @@ namespace Exiv2 { ErrMsg( 32, N_("Setting %1 in %2 images is not supported")), // %1=metadata type, %2=image format ErrMsg( 33, N_("This does not look like a CRW image")), ErrMsg( 34, N_("%1: Not supported")), // %1=function - ErrMsg( 35, N_("Unknown XMP prefix `%1'")), // %1=prefix + ErrMsg( 35, N_("No namespace info available for XMP prefix `%1'")), // %1=prefix ErrMsg( 36, N_("No XMP property list for prefix `%1'")), // %1=prefix ErrMsg( 37, N_("Size of %1 JPEG segment is larger than 65535 bytes")), // %1=type of metadata (Exif, IPTC, JPEG comment) ErrMsg( 38, N_("Unknown XMP property `%1:%2'")), // %1=prefix, %2=property name - ErrMsg( 39, N_("XMP Toolkit error %1: %2")), // %1=XMP_Error::GetID(), %2=XMP_Error::GetErrMsg() + ErrMsg( 39, N_("Unhandled XMP node %1 with opt=%2")), // %1=key, %2=XMP Toolkit option flags + ErrMsg( 40, N_("XMP Toolkit error %1: %2")), // %1=XMP_Error::GetID(), %2=XMP_Error::GetErrMsg() + ErrMsg( 41, N_("Failed to decode Lang Alt property %1 with opt=%2")), // %1=property path, %3=XMP Toolkit option flags + ErrMsg( 42, N_("Failed to decode Lang Alt qualifier %1 with opt=%2")), // %1=qualifier path, %3=XMP Toolkit option flags + ErrMsg( 43, N_("Failed to encode Lang Alt property %1")), // %1=key + ErrMsg( 44, N_("Failed to determine property name from path %1, namespace %2")), // %1=property path, %2=namespace + ErrMsg( 45, N_("Schema namespace %1 is not registered with the XMP Toolkit")), // %1=namespace + ErrMsg( 46, N_("No namespace registered for prefix `%1'")), // %1=prefix + ErrMsg( 47, N_("No prefix registered for namespace `%1'")), // %1=namespace + // Last error message (message is not used) ErrMsg( -2, N_("(Unknown Error)")) }; diff --git a/src/exiv2.cpp b/src/exiv2.cpp index eacc7390..9ac9771b 100644 --- a/src/exiv2.cpp +++ b/src/exiv2.cpp @@ -42,6 +42,7 @@ EXIV2_RCSID("@(#) $Id$") #include "actions.hpp" #include "utils.hpp" #include "i18n.h" // NLS support. +#include "xmp.hpp" #include #include @@ -154,6 +155,7 @@ int main(int argc, char* const argv[]) taskFactory.cleanup(); params.cleanup(); + Exiv2::XmpParser::terminate(); return rc; } // main diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp index 64de2525..89492a8c 100644 --- a/src/jpgimage.cpp +++ b/src/jpgimage.cpp @@ -291,11 +291,10 @@ namespace Exiv2 { io_->read(xmpPacket.pData_, xmpPacket.size_); if (io_->error() || io_->eof()) throw Error(14); xmpPacket_.assign(reinterpret_cast(xmpPacket.pData_), xmpPacket.size_); - if (XmpParser::decode(xmpData_, xmpPacket_)) { + if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_)) { #ifndef SUPPRESS_WARNINGS std::cerr << "Warning: Failed to decode XMP metadata.\n"; #endif - xmpData_.clear(); } --search; } @@ -394,7 +393,7 @@ namespace Exiv2 { throw Error(22); } - const long bufMinSize = 31; + const long bufMinSize = 36; long bufRead = 0; DataBuf buf(bufMinSize); const long seek = io_->tell(); @@ -468,8 +467,7 @@ namespace Exiv2 { } if (exifData_.count() > 0) ++search; - // Todo: Update here when merging branches/xmp - if (xmpPacket_.size() > 0) ++search; + if (xmpData_.count() > 0) ++search; if (iptcData_.count() > 0) ++search; if (!comment_.empty()) ++search; @@ -526,22 +524,28 @@ namespace Exiv2 { --search; } } - // Todo: Update here when merging branches/xmp - if (xmpPacket_.size() > 0) { - // Write APP1 marker, size of APP1 field, XMP id and XMP packet - tmpBuf[0] = 0xff; - tmpBuf[1] = app1_; + if (xmpData_.count() > 0) { + if (XmpParser::encode(xmpPacket_, xmpData_)) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to encode XMP metadata.\n"; +#endif + } + if (xmpPacket_.size() > 0) { + // Write APP1 marker, size of APP1 field, XMP id and XMP packet + tmpBuf[0] = 0xff; + tmpBuf[1] = app1_; - if (xmpPacket_.size() + 31 > 0xffff) throw Error(37, "XMP"); - us2Data(tmpBuf + 2, static_cast(xmpPacket_.size() + 31), bigEndian); - memcpy(tmpBuf + 4, xmpId_, 29); - if (outIo.write(tmpBuf, 33) != 33) throw Error(21); + if (xmpPacket_.size() + 31 > 0xffff) throw Error(37, "XMP"); + us2Data(tmpBuf + 2, static_cast(xmpPacket_.size() + 31), bigEndian); + memcpy(tmpBuf + 4, xmpId_, 29); + if (outIo.write(tmpBuf, 33) != 33) throw Error(21); - // Write new XMP packet - if ( outIo.write(reinterpret_cast(xmpPacket_.data()), xmpPacket_.size()) - != static_cast(xmpPacket_.size())) throw Error(21); - if (outIo.error()) throw Error(21); - --search; + // Write new XMP packet + if ( outIo.write(reinterpret_cast(xmpPacket_.data()), xmpPacket_.size()) + != static_cast(xmpPacket_.size())) throw Error(21); + if (outIo.error()) throw Error(21); + --search; + } } if (psData.size_ > 0 || iptcData_.count() > 0) { // Set the new IPTC IRB, keeps existing IRBs but removes the diff --git a/src/pngchunk.cpp b/src/pngchunk.cpp index 2635a313..cfcb920f 100644 --- a/src/pngchunk.cpp +++ b/src/pngchunk.cpp @@ -36,7 +36,7 @@ EXIV2_RCSID("@(#) $Id: pngchunk.cpp 823 2006-06-23 07:35:00Z cgilles $") # include "exv_conf.h" #endif -#define DEBUG 1 +//#define DEBUG 1 // some defines to make it easier #define PNG_CHUNK_TYPE(data, index) &data[index+4] diff --git a/src/properties.cpp b/src/properties.cpp index e93534f3..e437f1bf 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -52,11 +52,14 @@ namespace Exiv2 { extern const XmpPropertyInfo xmpXmpMMInfo[]; extern const XmpPropertyInfo xmpXmpBJInfo[]; extern const XmpPropertyInfo xmpXmpTPgInfo[]; - extern const XmpPropertyInfo xmpPhotoshopInfo[]; extern const XmpPropertyInfo xmpXmpDMInfo[]; extern const XmpPropertyInfo xmpPdfInfo[]; + extern const XmpPropertyInfo xmpPhotoshopInfo[]; + extern const XmpPropertyInfo xmpCrsInfo[]; extern const XmpPropertyInfo xmpTiffInfo[]; extern const XmpPropertyInfo xmpExifInfo[]; + extern const XmpPropertyInfo xmpAuxInfo[]; + extern const XmpPropertyInfo xmpIptcInfo[]; extern const XmpNsInfo xmpNsInfo[] = { // Schemas @@ -69,11 +72,11 @@ namespace Exiv2 { { "http://ns.adobe.com/xmp/1.0/DynamicMedia/", "xmpDM", xmpXmpDMInfo, "XMP Dynamic Media schema" }, { "http://ns.adobe.com/pdf/1.3/", "pdf", xmpPdfInfo, "Adobe PDF schema" }, { "http://ns.adobe.com/photoshop/1.0/", "photoshop", xmpPhotoshopInfo, "Adobe photoshop schema" }, - { "http://ns.adobe.com/camera-raw-settings/1.0/", "crs", 0, "Camera Raw schema" }, + { "http://ns.adobe.com/camera-raw-settings/1.0/", "crs", xmpCrsInfo, "Camera Raw schema" }, { "http://ns.adobe.com/tiff/1.0/", "tiff", xmpTiffInfo, "Exif Schema for TIFF Properties" }, { "http://ns.adobe.com/exif/1.0/", "exif", xmpExifInfo, "Exif schema for Exif-specific Properties" }, - { "http://ns.adobe.com/exif/1.0/aux/", "aux", 0, "Exif schema for Additional Exif Properties" }, - { "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", "iptc" /*Iptc4xmpCore*/, 0, "IPTC Core schema" }, + { "http://ns.adobe.com/exif/1.0/aux/", "aux", xmpAuxInfo, "Exif schema for Additional Exif Properties" }, + { "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", "iptc", xmpIptcInfo, "IPTC Core schema" }, // 'Iptc4xmpCore' is just too long // Structures { "http://ns.adobe.com/xap/1.0/g/", "xapG", 0, "Colorant structure" }, @@ -326,6 +329,61 @@ namespace Exiv2 { { 0, 0, 0, invalidTypeId, xmpInternal, 0 } }; + //! crs:CropUnits + extern const TagDetails xmpCrsCropUnits[] = { + { 0, "pixels" }, + { 1, "inches" }, + { 2, "cm" } + }; + + extern const XmpPropertyInfo xmpCrsInfo[] = { + { "AutoBrightness", "AutoBrightness", "Boolean", xmpText, xmpExternal, "When true, \"Brightness\" is automatically adjusted." }, + { "AutoContrast", "AutoContrast", "Boolean", xmpText, xmpExternal, "When true, \"Contrast\" is automatically adjusted." }, + { "AutoExposure", "AutoExposure", "Boolean", xmpText, xmpExternal, "When true, \"Exposure\" is automatically adjusted." }, + { "AutoShadows", "AutoShadows", "Boolean", xmpText, xmpExternal, "When true,\"Shadows\" is automatically adjusted." }, + { "BlueHue", "BlueHue", "Integer", signedShort, xmpExternal, "\"Blue Hue\" setting. Range -100 to 100." }, + { "BlueSaturation", "BlueSaturation", "Integer", signedShort, xmpExternal, "\"Blue Saturation\" setting. Range -100 to +100." }, + { "Brightness", "Brightness", "Integer", unsignedShort, xmpExternal, "\"Brightness\" setting. Range 0 to +150." }, + { "CameraProfile", "CameraProfile", "Text", xmpText, xmpExternal, "\"Camera Profile\" setting." }, + { "ChromaticAberrationB", "ChromaticAberrationB", "Integer", signedShort, xmpExternal, "\"Chomatic Aberration, Fix Blue/Yellow Fringe\" setting. Range -100 to +100." }, + { "ChromaticAberrationR", "ChromaticAberrationR", "Integer", signedShort, xmpExternal, "\"Chomatic Aberration, Fix Red/Cyan Fringe\" setting. Range -100 to +100." }, + { "ColorNoiseReduction", "ColorNoiseReduction", "Integer", unsignedShort, xmpExternal, "\"Color Noise Reducton\" setting. Range 0 to +100." }, + { "Contrast", "Contrast", "Integer", signedShort, xmpExternal, "\"Contrast\" setting. Range -50 to +100." }, + { "CropTop", "CropTop", "Real", xmpText, xmpExternal, "When HasCrop is true, top of crop rectangle" }, + { "CropLeft", "CropLeft", "Real", xmpText, xmpExternal, "When HasCrop is true, left of crop rectangle." }, + { "CropBottom", "CropBottom", "Real", xmpText, xmpExternal, "When HasCrop is true, bottom of crop rectangle." }, + { "CropRight", "CropRight", "Real", xmpText, xmpExternal, "When HasCrop is true, right of crop rectangle." }, + { "CropAngle", "CropAngle", "Real", xmpText, xmpExternal, "When HasCrop is true, angle of crop rectangle." }, + { "CropWidth", "CropWidth", "Real", xmpText, xmpExternal, "Width of resulting cropped image in CropUnits units." }, + { "CropHeight", "CropHeight", "Real", xmpText, xmpExternal, "Height of resulting cropped image in CropUnits units." }, + { "CropUnits", "CropUnits", "Integer", unsignedShort, xmpExternal, "Units for CropWidth and CropHeight. 0=pixels, 1=inches, 2=cm" }, + { "Exposure", "Exposure", "Real", xmpText, xmpExternal, "\"Exposure\" setting. Range -4.0 to +4.0." }, + { "GreenHue", "GreenHue", "Integer", signedShort, xmpExternal, "\"Green Hue\" setting. Range -100 to +100." }, + { "GreenSaturation", "GreenSaturation", "Integer", signedShort, xmpExternal, "\"Green Saturation\" setting. Range -100 to +100." }, + { "HasCrop", "HasCrop", "Boolean", xmpText, xmpExternal, "When true, image has a cropping rectangle." }, + { "HasSettings", "HasSettings", "Boolean", xmpText, xmpExternal, "When true, non-default camera raw settings." }, + { "LuminanceSmoothing", "LuminanceSmoothing", "Integer", unsignedShort, xmpExternal, "\"Luminance Smoothing\" setting. Range 0 to +100." }, + { "RawFileName", "RawFileName", "Text", xmpText, xmpInternal, "File name fo raw file (not a complete path)." }, + { "RedHue", "RedHue", "Integer", signedShort, xmpExternal, "\"Red Hue\" setting. Range -100 to +100." }, + { "RedSaturation", "RedSaturation", "Integer", signedShort, xmpExternal, "\"Red Saturation\" setting. Range -100 to +100." }, + { "Saturation", "Saturation", "Integer", signedShort, xmpExternal, "\"Saturation\" setting. Range -100 to +100." }, + { "Shadows", "Shadows", "Integer", unsignedShort, xmpExternal, "\"Shadows\" setting. Range 0 to +100." }, + { "ShadowTint", "ShadowTint", "Integer", signedShort, xmpExternal, "\"Shadow Tint\" setting. Range -100 to +100." }, + { "Sharpness", "Sharpness", "Integer", unsignedShort, xmpExternal, "\"Sharpness\" setting. Range 0 to +100." }, + { "Temperature", "Temperature", "Integer", unsignedShort, xmpExternal, "\"Temperature\" setting. Range 2000 to 50000." }, + { "Tint", "Tint", "Integer", signedShort, xmpExternal, "\"Tint\" setting. Range -150 to +150." }, + { "ToneCurve", "ToneCurve", "Seq of points (Integer, Integer)", xmpText, xmpExternal, "Array of points (Integer, Integer) defining a \"Tone Curve\"." }, + { "ToneCurveName", "ToneCurveName", "Choice Text", xmpText, xmpInternal, "The name of the Tone Curve described by ToneCurve. One of: Linear, Medium Contrast, " + "Strong Contrast, Custom or a user-defined preset name." }, + { "Version", "Version", "Text", xmpText, xmpInternal, "Version of Camera Raw plugin." }, + { "VignetteAmount", "VignetteAmount", "Integer", signedShort, xmpExternal, "\"Vignetting Amount\" setting. Range -100 to +100." }, + { "VignetteMidpoint", "VignetteMidpoint", "Integer", unsignedShort, xmpExternal, "\"Vignetting Midpoint\" setting. Range 0 to +100." }, + { "WhiteBalance", "WhiteBalance", "Closed Choice Text", xmpText, xmpExternal, "\"White Balance\" setting. One of: As Shot, Auto, Daylight, Cloudy, Shade, Tungsten, " + "Fluorescent, Flash, Custom" }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + extern const XmpPropertyInfo xmpTiffInfo[] = { { "ImageWidth", "ImageWidth", "Integer", unsignedLong, xmpInternal, "TIFF tag 256, 0x100. Image width in pixels." }, { "ImageLength", "ImageLength", "Integer", unsignedLong, xmpInternal, "TIFF tag 257, 0x101. Image height in pixels." }, @@ -663,6 +721,44 @@ namespace Exiv2 { { 0, 0, 0, invalidTypeId, xmpInternal, 0 } }; + extern const XmpPropertyInfo xmpAuxInfo[] = { + { "Lens", "Lens", "Text", xmpText, xmpInternal, "A description of the lens used to take the photograph. For example, \"70-200 mm f/2.8-4.0\"." }, + { "SerialNumber", "SerialNumber", "Text", xmpText, xmpInternal, "The serial number of the camera or camera body used to take the photograph." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpIptcInfo[] = { + { "CiAdrCity", "Contact Info-City", "Text", xmpText, xmpExternal, "The contact information city part." }, + { "CiAdrCtry", "Contact Info-Country", "Text", xmpText, xmpExternal, "The contact information country part." }, + { "CiAdrExtadr", "Contact Info-Address", "Text", xmpText, xmpExternal, "The contact information address part. Comprises an optional company name and all required " + "information to locate the building or postbox to which mail should be sent." }, + { "CiAdrPcode", "Contact Info-Postal Code", "Text", xmpText, xmpExternal, "The contact information part denoting the local postal code." }, + { "CiAdrRegion", "Contact Info-State/Province", "Text", xmpText, xmpExternal, "The contact information part denoting regional information like state or province." }, + { "CiEmailWork", "Contact Info-Email", "Text", xmpText, xmpExternal, "The contact information email address part." }, + { "CiTelWork", "Contact Info-Phone", "Text", xmpText, xmpExternal, "The contact information phone number part." }, + { "CiUrlWork", "Contact Info-Web URL", "Text", xmpText, xmpExternal, "The contact information web address part." }, + { "CountryCode", "Country Code", "closed Choice of Text", xmpText, xmpExternal, "Code of the country the content is focussing on -- either the country shown in visual " + "media or referenced in text or audio media. This element is at the top/first level of " + "a top-down geographical hierarchy. The code should be taken from ISO 3166 two or three " + "letter code. The full name of a country should go to the \"Country\" element." }, + { "CreatorContactInfo", "Creator's Contact Info", "ContactInfo", xmpText, xmpExternal, "The creator's contact information provides all necessary information to get in contact " + "with the creator of this news object and comprises a set of sub-properties for proper addressing." }, + { "IntellectualGenre", "Intellectual Genre", "Text", xmpText, xmpExternal, "Describes the nature, intellectual or journalistic characteristic of a news object, not " + "specifically its content." }, + { "Location", "Location", "Text", xmpText, xmpExternal, "Name of a location the content is focussing on -- either the location shown in visual " + "media or referenced by text or audio media. This location name could either be the name " + "of a sublocation to a city or the name of a well known location or (natural) monument " + "outside a city. In the sense of a sublocation to a city this element is at the fourth " + "level of a top-down geographical hierarchy." }, + { "Scene", "IPTC Scene", "bag closed Choice of Text", xmpText, xmpExternal, "Describes the scene of a photo content. Specifies one or more terms from the IPTC " + "\"Scene-NewsCodes\". Each Scene is represented as a string of 6 digits in an unordered list." }, + { "SubjectCode", "IPTC Subject Code", "bag closed Choice of Text", xmpText, xmpExternal, "Specifies one or more Subjects from the IPTC \"Subject-NewsCodes\" taxonomy to " + "categorize the content. Each Subject is represented as a string of 8 digits in an unordered list." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + XmpNsInfo::Ns::Ns(const std::string& ns) : ns_(ns) { @@ -691,6 +787,44 @@ namespace Exiv2 { return n == name; } + XmpProperties::NsRegistry XmpProperties::nsRegistry_; + + void XmpProperties::registerNs(const std::string& ns, + const std::string& prefix) + { + std::string ns2 = ns; + if (ns2.substr(ns2.size() - 1, 1) != "/") ns2 += "/"; + nsRegistry_[ns2] = prefix; + } + + std::string XmpProperties::prefix(const std::string& ns) + { + std::string ns2 = ns; + if (ns2.substr(ns2.size() - 1, 1) != "/") ns2 += "/"; + NsRegistry::const_iterator i = nsRegistry_.find(ns2); + std::string p; + if (i != nsRegistry_.end()) { + p = i->second; + } + else { + const XmpNsInfo* xn = find(xmpNsInfo, XmpNsInfo::Ns(ns2)); + if (xn) p = std::string(xn->prefix_); + } + return p; + } + + std::string XmpProperties::ns(const std::string& prefix) + { + std::string n; + for (NsRegistry::const_iterator i = nsRegistry_.begin(); + i != nsRegistry_.end(); ++i) { + if (i->second == prefix) { + return i->first; + } + } + return nsInfo(prefix)->ns_; + } + const char* XmpProperties::propertyTitle(const XmpKey& key) { return propertyInfo(key)->title_; @@ -721,11 +855,6 @@ namespace Exiv2 { return pi; } - const char* XmpProperties::ns(const std::string& prefix) - { - return nsInfo(prefix)->ns_; - } - const char* XmpProperties::nsDesc(const std::string& prefix) { return nsInfo(prefix)->desc_; @@ -743,12 +872,6 @@ namespace Exiv2 { return xn; } - const char* XmpProperties::prefix(const std::string& ns) - { - const XmpNsInfo* xn = find(xmpNsInfo, XmpNsInfo::Ns(ns)); - return xn ? xn->prefix_ : 0; - } - void XmpProperties::printProperties(std::ostream& os, const std::string& prefix) { const XmpPropertyInfo* pl = propertyList(prefix); @@ -765,7 +888,7 @@ namespace Exiv2 { //! Internal Pimpl structure with private members and data of class XmpKey. struct XmpKey::Impl { - Impl(); //!< Default constructor + Impl() {} //!< Default constructor Impl(const std::string& prefix, const std::string& property); //!< Constructor /*! @@ -775,7 +898,7 @@ namespace Exiv2 { @throw Error if the key cannot be decomposed. */ - void decomposeKey(const XmpKey* self, const std::string& key); + void decomposeKey(const std::string& key); // DATA static const char* familyName_; //!< "Xmp" @@ -785,13 +908,13 @@ namespace Exiv2 { }; //! @endcond - XmpKey::Impl::Impl() - { - } - XmpKey::Impl::Impl(const std::string& prefix, const std::string& property) - : prefix_(prefix), property_(property) { + // Validate prefix + if (XmpProperties::ns(prefix).empty()) throw Error(46, prefix); + + property_ = property; + prefix_ = prefix; } const char* XmpKey::Impl::familyName_ = "Xmp"; @@ -799,14 +922,12 @@ namespace Exiv2 { XmpKey::XmpKey(const std::string& key) : p_(new Impl) { - p_->decomposeKey(this, key); + p_->decomposeKey(key); } XmpKey::XmpKey(const std::string& prefix, const std::string& property) : p_(new Impl(prefix, property)) { - // Validate prefix and property, throws - XmpProperties::propertyInfo(*this); } XmpKey::~XmpKey() @@ -861,12 +982,12 @@ namespace Exiv2 { return XmpProperties::propertyTitle(*this); } - const char* XmpKey::ns() const + std::string XmpKey::ns() const { return XmpProperties::ns(p_->prefix_); } - void XmpKey::Impl::decomposeKey(const XmpKey* self, const std::string& key) + void XmpKey::Impl::decomposeKey(const std::string& key) { // Get the family name, prefix and property name parts of the key std::string::size_type pos1 = key.find('.'); @@ -883,12 +1004,11 @@ namespace Exiv2 { std::string property = key.substr(pos1 + 1); if (property == "") throw Error(6, key); + // Validate prefix + if (XmpProperties::ns(prefix).empty()) throw Error(46, prefix); + property_ = property; prefix_ = prefix; - - // Validate prefix and property - XmpProperties::propertyInfo(*self); - } // XmpKey::Impl::decomposeKey // ************************************************************************* diff --git a/src/properties.hpp b/src/properties.hpp index 19dad8d1..e8c7a7cf 100644 --- a/src/properties.hpp +++ b/src/properties.hpp @@ -141,7 +141,7 @@ namespace Exiv2 { @return the namespace name @throw Error if no namespace is registered with \em prefix. */ - static const char* ns(const std::string& prefix); + static std::string ns(const std::string& prefix); /*! @brief Return the namespace description for the schema associated with \em prefix. @@ -167,14 +167,28 @@ namespace Exiv2 { */ static const XmpNsInfo* nsInfo(const std::string& prefix); /*! - @brief Return the (preferred) prefix for schema namespace \em ns + @brief Return the (preferred) prefix for schema namespace \em ns. @param ns Schema namespace - @return the prefix or 0 if namespace \em ns is not registered. + @return the prefix or an empty string if namespace \em ns is not + registered. */ - static const char* prefix(const std::string& ns); + static std::string prefix(const std::string& ns); //! Print a list of properties of a schema namespace to output stream \em os. static void printProperties(std::ostream& os, const std::string& prefix); + /*! + @brief Register namespace \em ns with preferred prefix \em prefix. + + If the namespace is a known or previously registered namespace, the + prefix is overwritten. This also invalidates XMP keys generated with + the previous prefix. + */ + static void registerNs(const std::string& ns, const std::string& prefix); + + private: + typedef std::map NsRegistry; + static NsRegistry nsRegistry_; + }; // class XmpProperties /*! @@ -192,8 +206,8 @@ namespace Exiv2 { @param key The key string. @throw Error if the first part of the key is not 'Xmp' or - the remaining parts of the key cannot be parsed and - converted to a known schema prefix and property name. + the second part of the key cannot be parsed and converted + to a known schema prefix. */ explicit XmpKey(const std::string& key); /*! @@ -203,8 +217,7 @@ namespace Exiv2 { @param prefix Schema prefix name @param property Property name - @throw Error if the schema prefix or the property name are not - known. + @throw Error if the schema prefix is not known. */ XmpKey(const std::string& prefix, const std::string& property); //! Copy constructor. @@ -237,7 +250,7 @@ namespace Exiv2 { // Todo: Should this be removed? What about tagLabel then? //! Return the schema namespace for the prefix of the key - const char* ns() const; + std::string ns() const; //@} private: diff --git a/src/types.cpp b/src/types.cpp index 3e593c16..b1fb33a1 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -69,10 +69,12 @@ namespace Exiv2 { TypeInfoTable(signedRational, "SRational", 8), TypeInfoTable(string, "String", 1), TypeInfoTable(date, "Date", 8), - TypeInfoTable(time, "Time", 11), + TypeInfoTable(time, "Time", 11), TypeInfoTable(comment, "Comment", 1), TypeInfoTable(directory, "Directory", 1), TypeInfoTable(xmpText, "XmpText", 1), + TypeInfoTable(xmpArray, "XmpArray", 1), + TypeInfoTable(langAlt, "LangAlt", 1), // End of list marker TypeInfoTable(lastTypeId, "(Unknown)", 0) }; diff --git a/src/types.hpp b/src/types.hpp index 557b357a..bf1350a9 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -102,7 +102,7 @@ namespace Exiv2 { string, date, time, comment, directory, - xmpText, + xmpText, xmpArray, langAlt, lastTypeId }; // Todo: decentralize IfdId, so that new ids can be defined elsewhere diff --git a/src/value.cpp b/src/value.cpp index 82117aaa..2597abe9 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -50,10 +50,16 @@ EXIV2_RCSID("@(#) $Id$") // class member definitions namespace Exiv2 { + Value::Value(TypeId typeId) + : ok_(true), type_(typeId) + { + } + Value& Value::operator=(const Value& rhs) { if (this == &rhs) return *this; type_ = rhs.type_; + ok_ = rhs.ok_; return *this; } @@ -109,6 +115,9 @@ namespace Exiv2 { case xmpText: value = AutoPtr(new XmpTextValue); break; + case langAlt: + value = AutoPtr(new LangAltValue); + break; default: value = AutoPtr(new DataValue(typeId)); break; @@ -125,6 +134,7 @@ namespace Exiv2 { { std::ostringstream os; write(os); + ok_ = !os.fail(); return os.str(); } @@ -133,14 +143,6 @@ namespace Exiv2 { return toString(); } - DataValue& DataValue::operator=(const DataValue& rhs) - { - if (this == &rhs) return *this; - Value::operator=(rhs); - value_ = rhs.value_; - return *this; - } - int DataValue::read(const byte* buf, long len, ByteOrder /*byteOrder*/) { // byteOrder not needed @@ -192,9 +194,28 @@ namespace Exiv2 { { std::ostringstream os; os << static_cast(value_[n]); + ok_ = !os.fail(); return os.str(); } + long DataValue::toLong(long n) const + { + ok_ = true; + return value_[n]; + } + + float DataValue::toFloat(long n) const + { + ok_ = true; + return value_[n]; + } + + Rational DataValue::toRational(long n) const + { + ok_ = true; + return Rational(value_[n], 1); + } + StringValueBase& StringValueBase::operator=(const StringValueBase& rhs) { if (this == &rhs) return *this; @@ -235,23 +256,27 @@ namespace Exiv2 { return os << value_; } - StringValue& StringValue::operator=(const StringValue& rhs) + long StringValueBase::toLong(long n) const { - if (this == &rhs) return *this; - StringValueBase::operator=(rhs); - return *this; + ok_ = true; + return value_[n]; } - StringValue* StringValue::clone_() const + float StringValueBase::toFloat(long n) const { - return new StringValue(*this); + ok_ = true; + return value_[n]; } - AsciiValue& AsciiValue::operator=(const AsciiValue& rhs) + Rational StringValueBase::toRational(long n) const { - if (this == &rhs) return *this; - StringValueBase::operator=(rhs); - return *this; + ok_ = true; + return Rational(value_[n], 1); + } + + StringValue* StringValue::clone_() const + { + return new StringValue(*this); } int AsciiValue::read(const std::string& buf) @@ -326,13 +351,6 @@ namespace Exiv2 { read(comment); } - CommentValue& CommentValue::operator=(const CommentValue& rhs) - { - if (this == &rhs) return *this; - StringValueBase::operator=(rhs); - return *this; - } - int CommentValue::read(const std::string& comment) { std::string c = comment; @@ -387,60 +405,43 @@ namespace Exiv2 { return new CommentValue(*this); } - XmpTextValue& XmpTextValue::operator=(const XmpTextValue& rhs) + XmpValue::XmpValue(TypeId typeId) + : Value(typeId), + xmpArrayType_(xaNone), + xmpStruct_(xsNone) + { + } + + XmpValue& XmpValue::operator=(const XmpValue& rhs) { if (this == &rhs) return *this; - Value::operator=(rhs); - value_ = rhs.value_; + xmpArrayType_ = rhs.xmpArrayType_; + xmpStruct_ = rhs.xmpStruct_; return *this; } - int XmpTextValue::read(const std::string& buf) + void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) { - std::string::size_type start = 0; - bool escaped = false; - - for (std::string::size_type i = 0; i < buf.size(); ++i) { - if (start == 0) { - // skip whitespace leading to the quote - if (isspace(buf[i])) continue; - // first character after that must be a quote - if (buf[i] == quoteChar[0]) { - start = i + 1; - continue; - } - return 1; - } - // look for the first unescaped quote - if (buf[i] == escapeChar[0] && !escaped) { - escaped = true; - continue; - } - if (buf[i] == quoteChar[0] && !escaped) { - value_.push_back(buf.substr(start, i - start)); - // remove escape characters - unescapeText(value_.back()); - start = 0; - continue; - } - escaped = false; - } - // check for premature end of string - if (escaped || start != 0) return 2; + xmpArrayType_ = xmpArrayType; + } - return 0; + void XmpValue::setXmpStruct(XmpStruct xmpStruct) + { + xmpStruct_ = xmpStruct; } - int XmpTextValue::read(const byte* buf, - long len, - ByteOrder /*byteOrder*/) + XmpValue::XmpArrayType XmpValue::xmpArrayType() const { - std::string s(reinterpret_cast(buf), len); - return read(s); + return xmpArrayType_; } - long XmpTextValue::copy(byte* buf, - ByteOrder /*byteOrder*/) const + XmpValue::XmpStruct XmpValue::xmpStruct() const + { + return xmpStruct_; + } + + long XmpValue::copy(byte* buf, + ByteOrder /*byteOrder*/) const { std::ostringstream os; write(os); @@ -449,51 +450,211 @@ namespace Exiv2 { return s.size(); } - long XmpTextValue::size() const + int XmpValue::read(const byte* buf, + long len, + ByteOrder /*byteOrder*/) + { + std::string s(reinterpret_cast(buf), len); + return read(s); + } + + long XmpValue::size() const { std::ostringstream os; write(os); return os.str().size(); } + XmpTextValue::XmpTextValue() + : XmpValue(xmpText) + { + } + + XmpTextValue::XmpTextValue(const std::string& buf) + : XmpValue(xmpText) + { + read(buf); + } + + int XmpTextValue::read(const std::string& buf) + { + value_ = buf; + return 0; + } + + XmpTextValue::AutoPtr XmpTextValue::clone() const + { + return AutoPtr(clone_()); + } + + long XmpTextValue::size() const + { + return static_cast(value_.size()); + } + + long XmpTextValue::count() const + { + return size(); + } + std::ostream& XmpTextValue::write(std::ostream& os) const + { + return os << value_; + } + + long XmpTextValue::toLong(long /*n*/) const + { + return stringTo(value_, ok_); + } + + float XmpTextValue::toFloat(long /*n*/) const + { + return stringTo(value_, ok_); + } + + Rational XmpTextValue::toRational(long /*n*/) const + { + return stringTo(value_, ok_); + } + + XmpTextValue* XmpTextValue::clone_() const + { + return new XmpTextValue(*this); + } + + XmpArrayValue::XmpArrayValue() + : XmpValue(xmpArray) + { + } + + int XmpArrayValue::read(const std::string& buf) + { + value_.push_back(buf); + return 0; + } + + XmpArrayValue::AutoPtr XmpArrayValue::clone() const + { + return AutoPtr(clone_()); + } + + long XmpArrayValue::count() const + { + return static_cast(value_.size()); + } + + std::ostream& XmpArrayValue::write(std::ostream& os) const { for (std::vector::const_iterator i = value_.begin(); i != value_.end(); ++i) { - if (i != value_.begin()) os << " "; - std::string s(*i); - quoteText(s); - os << s; + if (i != value_.begin()) os << ", "; + os << *i; } return os; } - std::string XmpTextValue::toString(long n) const + std::string XmpArrayValue::toString(long n) const { + ok_ = true; return value_[n]; } - long XmpTextValue::toLong(long n) const + long XmpArrayValue::toLong(long n) const { - bool ok; - return stringTo(value_[n], ok); + return stringTo(value_[n], ok_); } - float XmpTextValue::toFloat(long n) const + float XmpArrayValue::toFloat(long n) const { - bool ok; - return stringTo(value_[n], ok); + return stringTo(value_[n], ok_); } - Rational XmpTextValue::toRational(long n) const + Rational XmpArrayValue::toRational(long n) const { - bool ok; - return stringTo(value_[n], ok); + return stringTo(value_[n], ok_); } - XmpTextValue* XmpTextValue::clone_() const + XmpArrayValue* XmpArrayValue::clone_() const { - return new XmpTextValue(*this); + return new XmpArrayValue(*this); + } + + LangAltValue::LangAltValue() + : XmpValue(langAlt) + { + } + + LangAltValue::LangAltValue(const std::string& buf) + : XmpValue(langAlt) + { + read(buf); + } + + int LangAltValue::read(const std::string& buf) + { + std::string b = buf; + std::string lang = "x-default"; + if (buf.length() > 5 && buf.substr(0, 5) == "lang=") { + std::string::size_type pos = buf.find_first_of(' '); + lang = buf.substr(5, pos-5); + // Strip quotes (so you can also specify the language without quotes) + if (lang[0] == '"') lang = lang.substr(1); + if (lang[lang.length()-1] == '"') lang = lang.substr(0, lang.length()-1); + b.clear(); + if (pos != std::string::npos) b = buf.substr(pos+1); + } + value_[lang] = b; + return 0; + } + + LangAltValue::AutoPtr LangAltValue::clone() const + { + return AutoPtr(clone_()); + } + + long LangAltValue::count() const + { + return static_cast(value_.size()); + } + + std::ostream& LangAltValue::write(std::ostream& os) const + { + for (ValueType::const_iterator i = value_.begin(); + i != value_.end(); ++i) { + if (i != value_.begin()) os << ", "; + os << "lang=\"" << i->first << "\" " + << i->second; + } + return os; + } + + std::string LangAltValue::toString(long /*n*/) const + { + ok_ = false; + return ""; + } + + long LangAltValue::toLong(long /*n*/) const + { + ok_ = false; + return 0; + } + + float LangAltValue::toFloat(long /*n*/) const + { + ok_ = false; + return 0.0; + } + + Rational LangAltValue::toRational(long /*n*/) const + { + ok_ = false; + return Rational(0, 0); + } + + LangAltValue* LangAltValue::clone_() const + { + return new LangAltValue(*this); } DateValue::DateValue(int year, int month, int day) @@ -504,16 +665,6 @@ namespace Exiv2 { date_.day = day; } - DateValue& DateValue::operator=(const DateValue& rhs) - { - if (this == &rhs) return *this; - Value::operator=(rhs); - date_.year = rhs.date_.year; - date_.month = rhs.date_.month; - date_.day = rhs.date_.day; - return *this; - } - int DateValue::read(const byte* buf, long len, ByteOrder /*byteOrder*/) { // Hard coded to read Iptc style dates @@ -602,7 +753,9 @@ namespace Exiv2 { tms.tm_mday = date_.day; tms.tm_mon = date_.month - 1; tms.tm_year = date_.year - 1900; - return static_cast(std::mktime(&tms)); + long l = static_cast(std::mktime(&tms)); + ok_ = (l != -1); + return l; } TimeValue::TimeValue(int hour, int minute, @@ -610,19 +763,11 @@ namespace Exiv2 { int tzMinute) : Value(date) { - time_.hour=hour; - time_.minute=minute; - time_.second=second; - time_.tzHour=tzHour; - time_.tzMinute=tzMinute; - } - - TimeValue& TimeValue::operator=(const TimeValue& rhs) - { - if (this == &rhs) return *this; - Value::operator=(rhs); - memcpy(&time_, &rhs.time_, sizeof(time_)); - return *this; + time_.hour = hour; + time_.minute = minute; + time_.second = second; + time_.tzHour = tzHour; + time_.tzMinute = tzMinute; } int TimeValue::read(const byte* buf, long len, ByteOrder /*byteOrder*/) @@ -760,34 +905,8 @@ namespace Exiv2 { if (result < 0) { result += 86400; } + ok_ = true; return result; } -// ***************************************************************************** -// free functions - - const char quoteChar[] = "\""; - const char escapeChar[] = "\\"; - - void quoteText(std::string& text) - { - for (std::string::iterator i = text.begin(); i != text.end(); ++i) { - if (*i == escapeChar[0] || *i == quoteChar[0]) { - i = text.insert(i, escapeChar[0]); - if (++i == text.end()) break; - } - } - text = quoteChar + text + quoteChar; - } // quoteText - - void unescapeText(std::string& text) - { - for (std::string::iterator i = text.begin(); i != text.end(); ++i) { - if (*i == escapeChar[0]) { - i = text.erase(i); // returns next pos, i.e., skips the escaped char - if (i == text.end()) break; - } - } - } // unescapeText - } // namespace Exiv2 diff --git a/src/value.hpp b/src/value.hpp index 54be0c73..f2d836aa 100644 --- a/src/value.hpp +++ b/src/value.hpp @@ -38,6 +38,7 @@ // + standard includes #include #include +#include #include #include #include @@ -52,11 +53,10 @@ namespace Exiv2 { /*! @brief Common interface for all types of values used with metadata. - The interface provides a uniform way to access values independent from + The interface provides a uniform way to access values independent of their actual C++ type for simple tasks like reading the values from a string or data buffer. For other tasks, like modifying values you may - need to downcast it to the actual subclass of %Value so that you can - access the subclass specific interface. + need to downcast it to a specific subclass to access its interface. */ class Value { public: @@ -66,15 +66,10 @@ namespace Exiv2 { //! @name Creators //@{ //! Constructor, taking a type id to initialize the base class with - explicit Value(TypeId typeId) - : type_(typeId) {} - //! Copy constructor - Value(const Value& rhs) - : type_(rhs.type_) {} + explicit Value(TypeId typeId); //! Virtual destructor. virtual ~Value() {} //@} - //! @name Manipulators //@{ /*! @@ -196,6 +191,11 @@ namespace Exiv2 { DataBuf if the value does not have a data area assigned. */ virtual DataBuf dataArea() const { return DataBuf(0, 0); }; + /*! + @brief Check the \em ok status indicator. After a to conversion, + this indicator shows whether the conversion was successful. + */ + bool ok() const { return ok_; } //@} /*! @@ -220,6 +220,8 @@ namespace Exiv2 { time%TimeValue comment%CommentValue xmpText%XmpTextValue + xmpArray%XmpArrayValue + langAlt%LangAltValue default:%DataValue(typeId) @@ -235,12 +237,14 @@ namespace Exiv2 { by subclasses but not directly. */ Value& operator=(const Value& rhs); + // DATA + mutable bool ok_; //!< Indicates the status of the previous to conversion private: //! Internal virtual copy constructor. virtual Value* clone_() const =0; // DATA - TypeId type_; //!< Type of the data + TypeId type_; //!< Type of the data }; // class Value @@ -259,7 +263,7 @@ namespace Exiv2 { //! @name Creators //@{ //! Default constructor. - DataValue(TypeId typeId =undefined) : Value(typeId) {} + explicit DataValue(TypeId typeId =undefined) : Value(typeId) {} //! Constructor DataValue(const byte* buf, long len, ByteOrder byteOrder =invalidByteOrder, @@ -271,8 +275,6 @@ namespace Exiv2 { //! @name Manipulators //@{ - //! Assignment operator. - DataValue& operator=(const DataValue& rhs); /*! @brief Read the value from a character buffer. @@ -318,17 +320,20 @@ namespace Exiv2 { n-th component. */ virtual std::string toString(long n) const; - virtual long toLong(long n =0) const { return value_[n]; } - virtual float toFloat(long n =0) const { return value_[n]; } - virtual Rational toRational(long n =0) const - { return Rational(value_[n], 1); } + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const; + virtual Rational toRational(long n =0) const; //@} private: //! Internal virtual copy constructor. virtual DataValue* clone_() const; + + public: + //! Type used to store the data. + typedef std::vector ValueType; // DATA - std::vector value_; + ValueType value_; }; // class DataValue @@ -346,7 +351,7 @@ namespace Exiv2 { //! @name Creators //@{ //! Constructor for subclasses - StringValueBase(TypeId typeId) + explicit StringValueBase(TypeId typeId) : Value(typeId) {} //! Constructor for subclasses StringValueBase(TypeId typeId, const std::string& buf) @@ -361,8 +366,6 @@ namespace Exiv2 { //! @name Manipulators //@{ - //! Assignment operator. - StringValueBase& operator=(const StringValueBase& rhs); //! Read the value from buf. This default implementation uses buf as it is. virtual int read(const std::string& buf); /*! @@ -378,8 +381,8 @@ namespace Exiv2 { @return 0 if successful. */ virtual int read(const byte* buf, - long len, - ByteOrder byteOrder =invalidByteOrder); + long len, + ByteOrder byteOrder =invalidByteOrder); //@} //! @name Accessors @@ -401,16 +404,19 @@ namespace Exiv2 { virtual long copy(byte* buf, ByteOrder byteOrder =invalidByteOrder) const; virtual long count() const { return size(); } virtual long size() const; - virtual long toLong(long n =0) const { return value_[n]; } - virtual float toFloat(long n =0) const { return value_[n]; } - virtual Rational toRational(long n =0) const - { return Rational(value_[n], 1); } + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const; + virtual Rational toRational(long n =0) const; virtual std::ostream& write(std::ostream& os) const; //@} protected: + //! Assignment operator. + StringValueBase& operator=(const StringValueBase& rhs); //! Internal virtual copy constructor. virtual StringValueBase* clone_() const =0; + + public: // DATA std::string value_; //!< Stores the string value. @@ -434,20 +440,12 @@ namespace Exiv2 { StringValue() : StringValueBase(string) {} //! Constructor - StringValue(const std::string& buf) + explicit StringValue(const std::string& buf) : StringValueBase(string, buf) {} - //! Copy constructor - StringValue(const StringValue& rhs) - : StringValueBase(rhs) {} //! Virtual destructor. virtual ~StringValue() {} //@} - //! @name Manipulators - //@{ - StringValue& operator=(const StringValue& rhs); - //@} - //! @name Accessors //@{ AutoPtr clone() const { return AutoPtr(clone_()); } @@ -476,19 +474,14 @@ namespace Exiv2 { AsciiValue() : StringValueBase(asciiString) {} //! Constructor - AsciiValue(const std::string &buf) + explicit AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) {} - //! Copy constructor - AsciiValue(const AsciiValue& rhs) - : StringValueBase(rhs) {} //! Virtual destructor. virtual ~AsciiValue() {} //@} //! @name Manipulators //@{ - //! Assignment operator - AsciiValue& operator=(const AsciiValue& rhs); /*! @brief Set the value to that of the string buf. Overrides base class to append a terminating '\\0' character if buf doesn't end @@ -569,18 +562,13 @@ namespace Exiv2 { CommentValue() : StringValueBase(Exiv2::undefined) {} //! Constructor, uses read(const std::string& comment) - CommentValue(const std::string& comment); - //! Copy constructor - CommentValue(const CommentValue& rhs) - : StringValueBase(rhs) {} + explicit CommentValue(const std::string& comment); //! Virtual destructor. virtual ~CommentValue() {} //@} //! @name Manipulators //@{ - //! Assignment operator. - CommentValue& operator=(const CommentValue& rhs); /*! @brief Read the value from a comment @@ -617,44 +605,52 @@ namespace Exiv2 { }; // class CommentValue /*! - @brief %Value type suitable for XMP Text properties and arrays thereof. - - Uses a vector of std::string to store the text(s). + @brief Base class for all Exiv2 values used to store XMP property values. */ - class XmpTextValue : public Value { + class XmpValue : public Value { public: - //! Shortcut for a %XmpTextValue auto pointer. - typedef std::auto_ptr AutoPtr; + //! Shortcut for a %XmpValue auto pointer. + typedef std::auto_ptr AutoPtr; + + //! XMP array types. + enum XmpArrayType { xaNone, xaAlt, xaBag, xaSeq }; + //! XMP structure indicator. + enum XmpStruct { xsNone, xsStruct }; //! @name Creators //@{ - //! Constructor for subclasses - XmpTextValue() - : Value(xmpText) {} - //! Constructor for subclasses - XmpTextValue(const std::string& buf) - : Value(xmpText) { read(buf); } - //! Copy constructor - XmpTextValue(const XmpTextValue& rhs) - : Value(rhs), value_(rhs.value_) {} - - //! Virtual destructor. - virtual ~XmpTextValue() {} + explicit XmpValue(TypeId typeId); //@} - //! @name Manipulators + //! @name Accessors //@{ - //! Assignment operator. - XmpTextValue& operator=(const XmpTextValue& rhs); + //! Return XMP array type, indicates if an XMP value is an array. + XmpArrayType xmpArrayType() const; + //! Return XMP struct, indicates if an XMP value is a structure. + XmpStruct xmpStruct() const; + virtual long size() const; /*! - @brief Read a text property value or array items from \em buf. + @brief Write value to a character data buffer. - Expects a list of quoted strings, one for each array item. The - quoted strings may be separated by whitespace. Double quotes in - the strings must be escaped with a backslash character: \\". - Examples: ""\\"foo\\", he said"" or ""foo" "bar"". - */ - virtual int read(const std::string& buf); + The user must ensure that the buffer has enough memory. Otherwise + the call results in undefined behaviour. + + @note The byte order is required by the interface but not used by this + method, so just use the default. + + @param buf Data buffer to write to. + @param byteOrder Byte order. Not used. + @return Number of characters written. + */ + virtual long copy(byte* buf, ByteOrder byteOrder =invalidByteOrder) const; + //@} + + //! @name Manipulators + //@{ + //! Set the XMP array type to indicate that an XMP value is an array. + void setXmpArrayType(XmpArrayType xmpArrayType); + //! Set the XMP struct type to indicate that an XMP value is a structure. + void setXmpStruct(XmpStruct xmpStruct); /*! @brief Read the value from a character buffer. @@ -672,27 +668,126 @@ namespace Exiv2 { virtual int read(const byte* buf, long len, ByteOrder byteOrder =invalidByteOrder); + virtual int read(const std::string& buf) =0; + //@} + + protected: + /*! + @brief Assignment operator. Protected so that it can only be used + by subclasses but not directly. + */ + XmpValue& operator=(const XmpValue& rhs); + + private: + // DATA + XmpArrayType xmpArrayType_; //!< Type of XMP array + XmpStruct xmpStruct_; //!< XMP structure indicator + + }; // class XmpValue + + /*! + @brief %Value type suitable for simple XMP properties and + XMP nodes of complex types which are not parsed into + specific values. + + Uses a std::string to store the value. + */ + class XmpTextValue : public XmpValue { + public: + //! Shortcut for a %XmpTextValue auto pointer. + typedef std::auto_ptr AutoPtr; + + //! @name Creators + //@{ + //! Constructor. + XmpTextValue(); + //! Constructor, reads the value from a string. + explicit XmpTextValue(const std::string& buf); + //@} + + //! @name Manipulators + //@{ + virtual int read(const std::string& buf); //@} //! @name Accessors //@{ - AutoPtr clone() const { return AutoPtr(clone_()); } + AutoPtr clone() const; + long size() const; + virtual long count() const; /*! - @brief Write value to a character data buffer. + @brief Convert the value to a long. + The optional parameter \em n is not used and is ignored. - The user must ensure that the buffer has enough memory. Otherwise - the call results in undefined behaviour. + @return The converted value. + */ + virtual long toLong(long n =0) const; + /*! + @brief Convert the value to a float. + The optional parameter \em n is not used and is ignored. - @note The byte order is required by the interface but not used by this - method, so just use the default. + @return The converted value. + */ + virtual float toFloat(long n =0) const; + /*! + @brief Convert the value to a Rational. + The optional parameter \em n is not used and is ignored. - @param buf Data buffer to write to. - @param byteOrder Byte order. Not used. - @return Number of characters written. - */ - virtual long copy(byte* buf, ByteOrder byteOrder =invalidByteOrder) const; - virtual long count() const { return static_cast(value_.size()); } - virtual long size() const; + @return The converted value. + */ + virtual Rational toRational(long n =0) const; + virtual std::ostream& write(std::ostream& os) const; + //@} + + private: + //! Internal virtual copy constructor. + virtual XmpTextValue* clone_() const; + + public: + // DATA + std::string value_; //!< Stores the string values. + + }; // class XmpTextValue + + /*! + @brief %Value type for simple arrays. Each item in the array is a simple + value, without qualifiers. The array may be an ordered (\em seq), + unordered (\em bag) or alternative array (\em alt). The array + items must not contain qualifiers. For language alternatives use + LangAltValue. + + Uses a vector of std::string to store the value(s). + */ + class XmpArrayValue : public XmpValue { + public: + //! Shortcut for a %XmpArrayValue auto pointer. + typedef std::auto_ptr AutoPtr; + + //! @name Creators + //@{ + //! Constructor. + XmpArrayValue(); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Read a simple property value from \em buf and append it + to the value. + + Appends \em buf to the value after the last existing array element. + Subsequent calls will therefore populate multiple array elements in + the order they are read. + + @return 0 if successful. + */ + virtual int read(const std::string& buf); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const; + virtual long count() const; /*! @brief Return the n-th component of the value as a string. The behaviour of this method may be undefined if there is no @@ -702,16 +797,105 @@ namespace Exiv2 { virtual long toLong(long n =0) const; virtual float toFloat(long n =0) const; virtual Rational toRational(long n =0) const; + /*! + @brief Write all elements of the value to \em os, separated by commas. + + @note The output of this method cannot directly be used as the parameter + for read(). + */ virtual std::ostream& write(std::ostream& os) const; //@} - protected: + private: //! Internal virtual copy constructor. - virtual XmpTextValue* clone_() const; + virtual XmpArrayValue* clone_() const; + + public: + //! Type used to store XMP array elements. + typedef std::vector ValueType; // DATA std::vector value_; //!< Stores the string values. - }; // class XmpTextValue + }; // class XmpArrayValue + + /*! + @brief %Value type for XMP language alternative properties. + + A language alternative is an array consisting of simple text values, + each of which has a language qualifier. + */ + class LangAltValue : public XmpValue { + public: + //! Shortcut for a %LangAltValue auto pointer. + typedef std::auto_ptr AutoPtr; + + //! @name Creators + //@{ + //! Constructor. + LangAltValue(); + //! Constructor, reads the value from a string. + explicit LangAltValue(const std::string& buf); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Read a simple property value from \em buf and append it + to the value. + + Appends \em buf to the value after the last existing array element. + Subsequent calls will therefore populate multiple array elements in + the order they are read. + + The format of \em buf is: +
+ [lang=["]language code["] ]text +
+ The XMP default language code x-default is used if + \em buf doesn't start with the keyword lang. + + @return 0 if successful. + */ + virtual int read(const std::string& buf); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const; + virtual long count() const; + /*! + @brief Return the n-th component of the value as a string. + The behaviour of this method may be undefined if there is no + n-th component. + */ + virtual std::string toString(long n) const; + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const; + virtual Rational toRational(long n =0) const; + /*! + @brief Write all elements of the value to \em os, separated by commas. + + @note The output of this method cannot directly be used as the parameter + for read(). + */ + virtual std::ostream& write(std::ostream& os) const; + //@} + + private: + //! Internal virtual copy constructor. + virtual LangAltValue* clone_() const; + + public: + //! Type used to store language alternative arrays. + typedef std::map ValueType; + // DATA + /*! + @brief Map to store the language alternative values. The language + qualifier is used as the key for the map entries. + */ + ValueType value_; + + }; // class LangAltValue /*! @brief %Value for simple ISO 8601 dates @@ -744,8 +928,6 @@ namespace Exiv2 { //! @name Manipulators //@{ - //! Assignment operator. - DateValue& operator=(const DateValue& rhs); /*! @brief Read the value from a character buffer. @@ -810,6 +992,7 @@ namespace Exiv2 { private: //! Internal virtual copy constructor. virtual DateValue* clone_() const; + // DATA Date date_; @@ -854,8 +1037,6 @@ namespace Exiv2 { //! @name Manipulators //@{ - //! Assignment operator. - TimeValue& operator=(const TimeValue& rhs); /*! @brief Read the value from a character buffer. @@ -991,7 +1172,7 @@ namespace Exiv2 { //! Constructor ValueType(const byte* buf, long len, ByteOrder byteOrder); //! Constructor - ValueType(const T& val, ByteOrder byteOrder =littleEndian); + explicit ValueType(const T& val, ByteOrder byteOrder =littleEndian); //! Copy constructor ValueType(const ValueType& rhs); //! Virtual destructor. @@ -1086,19 +1267,6 @@ namespace Exiv2 { // ***************************************************************************** // free functions, template and inline definitions - //! Double-quote character - extern const char quoteChar[]; - //! Escape character - extern const char escapeChar[]; - /*! - @brief Quote a string with double-quotes, escape quotes and escape - characters in the string. - */ - void quoteText(std::string& text); - /*! - @brief Remove escape characters from an unquoted string. - */ - void unescapeText(std::string& text); /*! @brief Read a value of type T from the data buffer. @@ -1330,6 +1498,7 @@ namespace Exiv2 { template inline std::string ValueType::toString(long n) const { + ok_ = true; return Exiv2::toString(value_[n]); } @@ -1337,54 +1506,63 @@ namespace Exiv2 { template inline long ValueType::toLong(long n) const { + ok_ = true; return value_[n]; } // Specialization for rational template<> inline long ValueType::toLong(long n) const { + ok_ = (value_[n].second != 0); return value_[n].first / value_[n].second; } // Specialization for unsigned rational template<> inline long ValueType::toLong(long n) const { + ok_ = (value_[n].second != 0); return value_[n].first / value_[n].second; } // Default implementation template inline float ValueType::toFloat(long n) const { + ok_ = true; return static_cast(value_[n]); } // Specialization for rational template<> inline float ValueType::toFloat(long n) const { + ok_ = (value_[n].second != 0); return static_cast(value_[n].first) / value_[n].second; } // Specialization for unsigned rational template<> inline float ValueType::toFloat(long n) const { + ok_ = (value_[n].second != 0); return static_cast(value_[n].first) / value_[n].second; } // Default implementation template inline Rational ValueType::toRational(long n) const { + ok_ = true; return Rational(value_[n], 1); } // Specialization for rational template<> inline Rational ValueType::toRational(long n) const { + ok_ = true; return Rational(value_[n].first, value_[n].second); } // Specialization for unsigned rational template<> inline Rational ValueType::toRational(long n) const { + ok_ = true; return Rational(value_[n].first, value_[n].second); } diff --git a/src/xmp.cpp b/src/xmp.cpp index d3f406d7..d1dc03df 100644 --- a/src/xmp.cpp +++ b/src/xmp.cpp @@ -69,6 +69,29 @@ namespace { }; // class FindXmpdatum +#ifdef EXV_HAVE_XMP_TOOLKIT + //! Convert XMP Toolkit struct option bit to Value::XmpStruct + Exiv2::XmpValue::XmpStruct xmpStruct(const XMP_OptionBits& opt); + + //! Convert Value::XmpStruct to XMP Toolkit array option bits + XMP_OptionBits xmpOptionBits(Exiv2::XmpValue::XmpStruct xs); + + //! Convert XMP Toolkit array option bits to Value::XmpArrayType + Exiv2::XmpValue::XmpArrayType xmpArrayType(const XMP_OptionBits& opt); + + //! Convert Value::XmpArrayType to XMP Toolkit array option bits + XMP_OptionBits xmpOptionBits(Exiv2::XmpValue::XmpArrayType xat); + +#define DEBUG +# ifdef DEBUG + //! Print information about a parsed XMP node + void printNode(const std::string& schemaNs, + const std::string& propPath, + const std::string& propValue, + const XMP_OptionBits& opt); +# endif // DEBUG +#endif // EXV_HAVE_XMP_TOOLKIT + //! Make an XMP key from a schema namespace and property path Exiv2::XmpKey::AutoPtr makeXmpKey(const std::string& schemaNs, const std::string& propPath); @@ -342,77 +365,154 @@ namespace Exiv2 { bool XmpParser::initialized_ = false; + bool XmpParser::initialize() + { + if (!initialized_) { + initialized_ = SXMPMeta::Initialize(); + } + return initialized_; + } + + void XmpParser::terminate() + { + if (initialized_) { + SXMPMeta::Terminate(); + 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()) { + if (!initialize()) { #ifndef SUPPRESS_WARNINGS - std::cerr << "XMP Toolkit initialization failed.\n"; + std::cerr << "XMP Toolkit initialization failed.\n"; #endif - return 2; - } + 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); +#ifdef DEBUG + printNode(schemaNs, propPath, propValue, opt); +#endif + if (XMP_PropIsAlias(opt)) { + // Todo: What should we do with these? Skip for now + continue; + } + if (XMP_NodeIsSchema(opt)) { + // Register unknown namespaces with Exiv2 + // (Namespaces are automatically registered with the XMP Toolkit) + if (XmpProperties::prefix(schemaNs).empty()) { + std::string prefix; + bool ret = meta.GetNamespacePrefix(schemaNs.c_str(), &prefix); + if (!ret) throw Exiv2::Error(45, schemaNs); + prefix = prefix.substr(0, prefix.size() - 1); + XmpProperties::registerNs(schemaNs, prefix); } + continue; } - 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; + XmpKey::AutoPtr key = makeXmpKey(schemaNs, propPath); + if (XMP_ArrayIsAltText(opt)) { + // Read Lang Alt property + LangAltValue::AutoPtr val(new LangAltValue); + XMP_Index count = meta.CountArrayItems(schemaNs.c_str(), propPath.c_str()); + while (count-- > 0) { + // Get the text + bool haveNext = iter.Next(&schemaNs, &propPath, &propValue, &opt); +#ifdef DEBUG + printNode(schemaNs, propPath, propValue, opt); +#endif + if ( !haveNext + || !XMP_PropIsSimple(opt) + || !XMP_PropHasLang(opt)) { + throw Error(41, propPath, opt); + } + const std::string text = propValue; + // Get the language qualifier + haveNext = iter.Next(&schemaNs, &propPath, &propValue, &opt); +#ifdef DEBUG + printNode(schemaNs, propPath, propValue, opt); +#endif + if ( !haveNext + || !XMP_PropIsSimple(opt) + || !XMP_PropIsQualifier(opt) + || propPath.substr(propPath.size() - 8, 8) != "xml:lang") { + throw Error(42, propPath, opt); + } + val->value_[propValue] = text; } - iter.Skip(kXMP_IterSkipSubtree); - // Todo: Do not use read() for XmpTextValues - val->read(arrayValue); + xmpData.add(*key.get(), val.get()); + continue; } - else { -#ifndef SUPPRESS_WARNINGS - std::cerr << "Warning: XMP property " << key->key() - << " has unsupported property type; skipping property.\n"; + if ( XMP_PropIsArray(opt) + && !XMP_PropHasQualifiers(opt) + && !XMP_ArrayIsAltText(opt)) { + // Check if all elements are simple + bool simpleArray = true; + SXMPIterator aIter(meta, schemaNs.c_str(), propPath.c_str()); + std::string aSchemaNs, aPropPath, aPropValue; + XMP_OptionBits aOpt; + while (aIter.Next(&aSchemaNs, &aPropPath, &aPropValue, &aOpt)) { + if (propPath == aPropPath) continue; + if ( !XMP_PropIsSimple(aOpt) + || XMP_PropHasQualifiers(aOpt) + || XMP_PropIsQualifier(aOpt) + || XMP_NodeIsSchema(aOpt) + || XMP_PropIsAlias(aOpt)) { + simpleArray = false; + + std::cerr << "NOT SO SIMPLE ==> " << propPath << ", " << aPropPath << "\n"; + + break; + } + } + if (simpleArray) { + // Read the array into an XmpArrayValue + XmpArrayValue::AutoPtr val(new XmpArrayValue); + XMP_Index count = meta.CountArrayItems(schemaNs.c_str(), propPath.c_str()); + val->setXmpArrayType(xmpArrayType(opt)); + while (count-- > 0) { + iter.Next(&schemaNs, &propPath, &propValue, &opt); +#ifdef DEBUG + printNode(schemaNs, propPath, propValue, opt); #endif - iter.Skip(kXMP_IterSkipSubtree); + val->read(propValue); + } + xmpData.add(*key.get(), val.get()); + continue; + } + } + XmpTextValue::AutoPtr val(new XmpTextValue); + if ( XMP_PropIsStruct(opt) + || XMP_PropIsArray(opt)) { + // Create a metadatum with only XMP options + val->setXmpArrayType(xmpArrayType(opt)); + val->setXmpStruct(xmpStruct(opt)); + xmpData.add(*key.get(), val.get()); continue; } - - xmpData.add(*key.get(), val.get()); - } + if ( XMP_PropIsSimple(opt) + || XMP_PropIsQualifier(opt)) { + val->read(propValue); + xmpData.add(*key.get(), val.get()); + continue; + } + // Don't let any node go by unnoticed + throw Error(39, key->key(), opt); + } // iterate through all XMP nodes return 0; } catch (const XMP_Error& e) { #ifndef SUPPRESS_WARNINGS - std::cerr << Error(39, e.GetID(), e.GetErrMsg()) << "\n"; + std::cerr << Error(40, e.GetID(), e.GetErrMsg()) << "\n"; #endif xmpData.clear(); return 3; @@ -436,55 +536,70 @@ namespace Exiv2 { { try { xmpPacket.clear(); - if (!initialized_) { - initialized_ = true; - if (!SXMPMeta::Initialize()) { + if (!initialize()) { #ifndef SUPPRESS_WARNINGS - std::cerr << "XMP Toolkit initialization failed.\n"; + std::cerr << "XMP Toolkit initialization failed.\n"; #endif - return 2; - } + return 2; } SXMPMeta meta; for (XmpData::const_iterator i = xmpData.begin(); i != xmpData.end(); ++i) { - const char* ns = XmpProperties::ns(i->groupName()); + std::string 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?) + // Todo: What about structure namespaces? - if (i->count() == 1) { - // simple property -//-ahu -std::cerr << i->key() << "\n"; - meta.SetProperty(ns, i->tagName().c_str(), i->toString(0).c_str()); + XMP_OptionBits options = 0; + if (i->typeId() == langAlt) { + // Encode Lang Alt property + const LangAltValue* la = dynamic_cast(&i->value()); + if (la == 0) throw Error(43, i->key()); + int idx = 1; + for (LangAltValue::ValueType::const_iterator k = la->value_.begin(); + k != la->value_.end(); ++k) { + meta.AppendArrayItem(ns.c_str(), i->tagName().c_str(), kXMP_PropArrayIsAltText, k->second.c_str()); + const std::string item = i->tagName() + "[" + toString(idx++) + "]"; + meta.SetQualifier(ns.c_str(), item.c_str(), kXMP_NS_XML, "lang", k->first.c_str()); + } + continue; } - 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()); + // Todo: Xmpdatum should have an XmpValue, not a Value + const XmpValue* val = dynamic_cast(&i->value()); + assert(val); + options = xmpOptionBits(val->xmpArrayType()) + | xmpOptionBits(val->xmpStruct()); + if (i->typeId() == xmpArray) { + meta.SetProperty(ns.c_str(), i->tagName().c_str(), 0, options); + for (int idx = 0; idx < i->count(); ++idx) { + const std::string item = i->tagName() + "[" + toString(idx + 1) + "]"; + + //-ahu + std::cerr << "Element " << item << " = " << i->toString(idx) << "\n"; + + meta.SetProperty(ns.c_str(), item.c_str(), i->toString(idx).c_str()); + } + } + if (i->typeId() == xmpText) { + if (i->count() == 0) { + meta.SetProperty(ns.c_str(), i->tagName().c_str(), 0, options); + } + else { + meta.SetProperty(ns.c_str(), i->tagName().c_str(), i->toString(0).c_str(), options); } } } meta.SerializeToBuffer(&xmpPacket, kXMP_UseCompactFormat); + return 0; } catch (const XMP_Error& e) { #ifndef SUPPRESS_WARNINGS - std::cerr << Error(39, e.GetID(), e.GetErrMsg()) << "\n"; + std::cerr << Error(40, e.GetID(), e.GetErrMsg()) << "\n"; #endif xmpPacket.clear(); return 3; @@ -514,45 +629,112 @@ std::cerr << i->key() << " count = " << i->count() << "\n"; // ***************************************************************************** // local definitions namespace { + +#ifdef EXV_HAVE_XMP_TOOLKIT + Exiv2::XmpValue::XmpStruct xmpStruct(const XMP_OptionBits& opt) + { + Exiv2::XmpValue::XmpStruct var(Exiv2::XmpValue::xsNone); + if (XMP_PropIsStruct(opt)) { + var = Exiv2::XmpValue::xsStruct; + } + return var; + } + + XMP_OptionBits xmpOptionBits(Exiv2::XmpValue::XmpStruct xs) + { + XMP_OptionBits var(0); + switch (xs) { + case Exiv2::XmpValue::xsNone: + break; + case Exiv2::XmpValue::xsStruct: + XMP_SetOption(var, kXMP_PropValueIsStruct); + break; + } + return var; + } + + Exiv2::XmpValue::XmpArrayType xmpArrayType(const XMP_OptionBits& opt) + { + Exiv2::XmpValue::XmpArrayType var(Exiv2::XmpValue::xaNone); + if (XMP_PropIsArray(opt)) { + if (XMP_ArrayIsAlternate(opt)) var = Exiv2::XmpValue::xaAlt; + else if (XMP_ArrayIsOrdered(opt)) var = Exiv2::XmpValue::xaSeq; + else if (XMP_ArrayIsUnordered(opt)) var = Exiv2::XmpValue::xaBag; + } + return var; + } + + XMP_OptionBits xmpOptionBits(Exiv2::XmpValue::XmpArrayType xat) + { + XMP_OptionBits var(0); + switch (xat) { + case Exiv2::XmpValue::xaNone: + break; + case Exiv2::XmpValue::xaAlt: + XMP_SetOption(var, kXMP_PropValueIsArray); + XMP_SetOption(var, kXMP_PropArrayIsAlternate); + break; + case Exiv2::XmpValue::xaSeq: + XMP_SetOption(var, kXMP_PropValueIsArray); + XMP_SetOption(var, kXMP_PropArrayIsOrdered); + break; + case Exiv2::XmpValue::xaBag: + XMP_SetOption(var, kXMP_PropValueIsArray); + break; + } + return var; + } + +# ifdef DEBUG + void printNode(const std::string& schemaNs, + const std::string& propPath, + const std::string& propValue, + const XMP_OptionBits& opt) + { + static bool first = true; + if (first) { + first = false; + std::cout << "ashisaas\n" + << "lcqqtrti\n"; + } + enum { alia=0, sche, hasq, isqu, stru, arra, lang, simp, len }; + std::string opts(len, '.'); + if (XMP_PropIsAlias(opt)) opts[alia] = 'X'; + if (XMP_NodeIsSchema(opt)) opts[sche] = 'X'; + if (XMP_PropHasQualifiers(opt)) opts[hasq] = 'X'; + if (XMP_PropIsQualifier(opt)) opts[isqu] = 'X'; + if (XMP_PropIsStruct(opt)) opts[stru] = 'X'; + if (XMP_PropIsArray(opt)) opts[arra] = 'X'; + if (XMP_ArrayIsAltText(opt)) opts[lang] = 'X'; + if (XMP_PropIsSimple(opt)) opts[simp] = 'X'; + + std::cout << opts << " "; + if (opts[sche] == 'X') { + std::cout << "ns=" << schemaNs; + } + else { + std::cout << propPath << " = " << propValue; + } + std::cout << std::endl; + } +# endif // DEBUG +#endif // EXV_HAVE_XMP_TOOLKIT + 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); + if (idx == std::string::npos) { + throw Exiv2::Error(44, propPath, schemaNs); } - 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 + // Don't worry about out_of_range, XMP parser takes care of this + property = propPath.substr(idx + 1); + std::string prefix = Exiv2::XmpProperties::prefix(schemaNs); + if (prefix.empty()) { + throw Exiv2::Error(47, propPath, schemaNs); } - return key; + return Exiv2::XmpKey::AutoPtr(new Exiv2::XmpKey(prefix, property)); } // makeXmpKey } diff --git a/src/xmp.hpp b/src/xmp.hpp index 36b30682..8fd40124 100644 --- a/src/xmp.hpp +++ b/src/xmp.hpp @@ -276,6 +276,23 @@ namespace Exiv2 { */ static int encode( std::string& xmpPacket, const XmpData& xmpData); + /*! + @brief Initialize the XMP Toolkit. + + Calling this method is usually not needed, as encode() and + decode() will initialize the XMP Toolkit if necessary. + + @return True if the initialization was successful, else false. + */ + static bool initialize(); + /*! + @brief Terminate the XMP Toolkit. + + Call this method when the XmpParser is no longer needed to + allow the XMP Toolkit to cleanly shutdown. + */ + static void terminate(); + private: static bool initialized_; //! Indicates if the XMP Toolkit has been initialized diff --git a/src/xmpparse.cpp b/src/xmpparse.cpp index bb456426..d23a439d 100644 --- a/src/xmpparse.cpp +++ b/src/xmpparse.cpp @@ -43,6 +43,7 @@ try { << std::dec << md->value() << std::endl; } + Exiv2::XmpParser::terminate(); return 0; } catch (Exiv2::AnyError& e) { diff --git a/src/xmpparser-test.cpp b/src/xmpparser-test.cpp index fad92eda..189cfd79 100644 --- a/src/xmpparser-test.cpp +++ b/src/xmpparser-test.cpp @@ -59,7 +59,7 @@ try { if (file.write(reinterpret_cast(xmpPacket.data()), xmpPacket.size()) == 0) { throw Exiv2::Error(2, filename, Exiv2::strError(), "FileIo::write"); } - + Exiv2::XmpParser::terminate(); return 0; } catch (Exiv2::AnyError& e) {