diff --git a/doc/cmdxmp.txt b/doc/cmdxmp.txt index 75bfa982..313f8ac5 100644 --- a/doc/cmdxmp.txt +++ b/doc/cmdxmp.txt @@ -54,6 +54,9 @@ set Xmp.tiff.ImageDescription lang=de-DE TIFF Bildbeschreibung # Register a namespace which Exiv2 doesn't know yet with a prefix. reg ns myNamespace/ +# Add a property in the new custom namespace. +set Xmp.ns.myProperty myValue + # There are no built-in Exiv2 value types for structures, qualifiers and # nested types. However, these can be added by using an XmpText value and a # path as the key. @@ -65,7 +68,7 @@ set Xmp.xmpDM.videoFrameSize/stDim:unit inch # Add an element with a qualifier (using the namespace registered earlier) set Xmp.dc.publisher James Bond -set Xmp.dc.publisher/?ns:role secret agent +set Xmp.dc.publisher[1]/?ns:role secret agent # Add a qualifer to an array element of Xmp.dc.creator (added above) set Xmp.dc.creator[2]/?ns:role programmer diff --git a/samples/xmpsample.cpp b/samples/xmpsample.cpp index 279f67f2..b8416dcb 100644 --- a/samples/xmpsample.cpp +++ b/samples/xmpsample.cpp @@ -75,6 +75,10 @@ try { // image, namespaces are decoded and registered at the same time. Exiv2::XmpProperties::registerNs("myNamespace/", "ns"); + // ------------------------------------------------------------------------- + // Add a property in the new custom namespace. + xmpData["Xmp.ns.myProperty"] = "myValue"; + // ------------------------------------------------------------------------- // There are no specialized values for structures, qualifiers and nested // types. However, these can be added by using an XmpTextValue and a path as diff --git a/src/actions.cpp b/src/actions.cpp index dc95f52c..2a9fd035 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1413,7 +1413,7 @@ namespace Action { std::cout << _("Reg ") << modifyCmd.key_ << "=\"" << modifyCmd.value_ << "\"" << std::endl; } - Exiv2::XmpProperties::registerNs(modifyCmd.value_, modifyCmd.key_); + // Registration has been done immediately after parsing the command. } Modify::AutoPtr Modify::clone() const diff --git a/src/exiv2.cpp b/src/exiv2.cpp index 6ded62f6..1ea76e5e 100644 --- a/src/exiv2.cpp +++ b/src/exiv2.cpp @@ -1015,6 +1015,12 @@ namespace { modifyCmd.explicitType_ = explicitType; modifyCmd.value_ = value; + if (cmdId == reg) { + // Registration needs to be done immediately as the new namespaces are + // looked up during parsing of subsequent lines (to validate XMP keys). + Exiv2::XmpProperties::registerNs(modifyCmd.value_, modifyCmd.key_); + } + return true; } // parseLine diff --git a/src/properties.cpp b/src/properties.cpp index 169ee042..f09eb78f 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -43,6 +43,8 @@ EXIV2_RCSID("@(#) $Id$") #include #include #include +#include +#include // ***************************************************************************** // class member definitions @@ -812,16 +814,57 @@ namespace Exiv2 { XmpProperties::NsRegistry XmpProperties::nsRegistry_; + const XmpNsInfo* XmpProperties::lookupNsRegistry(const XmpNsInfo::Prefix& prefix) + { + for (NsRegistry::const_iterator i = nsRegistry_.begin(); + i != nsRegistry_.end(); ++i) { + if (i->second == prefix) return &(i->second); + } + return 0; + } + void XmpProperties::registerNs(const std::string& ns, const std::string& prefix) { std::string ns2 = ns; if ( ns2.substr(ns2.size() - 1, 1) != "/" && ns2.substr(ns2.size() - 1, 1) != "#") ns2 += "/"; - nsRegistry_[ns2] = prefix; + // Allocated memory is freed when the namespace is unregistered. + // Using malloc/free for better system compatibility in case + // users don't unregister their namespaces explicitely. + XmpNsInfo xn; + char* c = static_cast(std::malloc(ns2.size() + 1)); + std::strcpy(c, ns2.c_str()); + xn.ns_ = c; + c = static_cast(std::malloc(prefix.size() + 1)); + std::strcpy(c, prefix.c_str()); + xn.prefix_ = c; + xn.xmpPropertyInfo_ = 0; + xn.desc_ = ""; + nsRegistry_[ns2] = xn; XmpParser::registerNs(ns2, prefix); } + void XmpProperties::unregisterNs(const std::string& ns) + { + NsRegistry::iterator i = nsRegistry_.find(ns); + if (i != nsRegistry_.end()) { + XmpParser::unregisterNs(ns); + std::free(const_cast(i->second.prefix_)); + std::free(const_cast(i->second.ns_)); + nsRegistry_.erase(i); + } + } + + void XmpProperties::unregisterNs() + { + NsRegistry::iterator i = nsRegistry_.begin(); + while (i != nsRegistry_.end()) { + NsRegistry::iterator kill = i++; + unregisterNs(kill->first); + } + } + std::string XmpProperties::prefix(const std::string& ns) { std::string ns2 = ns; @@ -829,7 +872,7 @@ namespace Exiv2 { NsRegistry::const_iterator i = nsRegistry_.find(ns2); std::string p; if (i != nsRegistry_.end()) { - p = i->second; + p = i->second.prefix_; } else { const XmpNsInfo* xn = find(xmpNsInfo, XmpNsInfo::Ns(ns2)); @@ -840,13 +883,8 @@ namespace Exiv2 { 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; - } - } + const XmpNsInfo* xn = lookupNsRegistry(XmpNsInfo::Prefix(prefix)); + if (xn != 0) return xn->ns_; return nsInfo(prefix)->ns_; } @@ -894,7 +932,9 @@ namespace Exiv2 { const XmpNsInfo* XmpProperties::nsInfo(const std::string& prefix) { - const XmpNsInfo* xn = find(xmpNsInfo, XmpNsInfo::Prefix(prefix)); + const XmpNsInfo::Prefix pf(prefix); + const XmpNsInfo* xn = lookupNsRegistry(pf); + if (!xn) xn = find(xmpNsInfo, pf); if (!xn) throw Error(35, prefix); return xn; } diff --git a/src/properties.hpp b/src/properties.hpp index 7a10252b..36ffd2c8 100644 --- a/src/properties.hpp +++ b/src/properties.hpp @@ -181,13 +181,33 @@ namespace Exiv2 { @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. + prefix is overwritten. + + @note This invalidates XMP keys generated with the previous prefix. */ static void registerNs(const std::string& ns, const std::string& prefix); + /*! + @brief Unregister a custom namespace \em ns. + + The function only has an effect if there is a namespace \em ns + registered earlier, it does not unregister built-in namespaces. + + @note This invalidates XMP keys generated in this namespace. + */ + static void unregisterNs(const std::string& ns); + /*! + @brief Unregister all custom namespaces. + + The function only unregisters namespaces registered earlier, it does not + unregister built-in namespaces. + + @note This invalidates XMP keys generated in any custom namespace. + */ + static void unregisterNs(); private: - typedef std::map NsRegistry; + typedef std::map NsRegistry; + static const XmpNsInfo* lookupNsRegistry(const XmpNsInfo::Prefix& prefix); static NsRegistry nsRegistry_; }; // class XmpProperties diff --git a/src/xmp.cpp b/src/xmp.cpp index 8fec92b6..96ec88ac 100644 --- a/src/xmp.cpp +++ b/src/xmp.cpp @@ -371,6 +371,7 @@ namespace Exiv2 { void XmpParser::terminate() { + XmpProperties::unregisterNs(); if (initialized_) { #ifdef EXV_HAVE_XMP_TOOLKIT SXMPMeta::Terminate(); @@ -394,7 +395,20 @@ namespace Exiv2 { initialize(); return true; #endif - } // XmpParser::registerNs + } // XmpParser::registerNs + + void XmpParser::unregisterNs(const std::string& ns) + { +#ifdef EXV_HAVE_XMP_TOOLKIT + try { +// Throws XMP Toolkit error 8: Unimplemented method XMPMeta::DeleteNamespace +// SXMPMeta::DeleteNamespace(ns.c_str()); + } + catch (const XMP_Error& e) { + throw Error(40, e.GetID(), e.GetErrMsg()); + } +#endif + } // XmpParser::unregisterNs #ifdef EXV_HAVE_XMP_TOOLKIT int XmpParser::decode( XmpData& xmpData, diff --git a/src/xmp.hpp b/src/xmp.hpp index b01a94c9..0cbc630c 100644 --- a/src/xmp.hpp +++ b/src/xmp.hpp @@ -257,6 +257,7 @@ namespace Exiv2 { */ class XmpParser { friend void XmpProperties::registerNs(const std::string&, const std::string&); + friend void XmpProperties::unregisterNs(const std::string&); public: /*! @brief Decode XMP metadata from an XMP packet \em xmpPacket into @@ -299,7 +300,7 @@ namespace Exiv2 { */ static bool initialize(); /*! - @brief Terminate the XMP Toolkit. + @brief Terminate the XMP Toolkit and unregister custom namespaces. Call this method when the XmpParser is no longer needed to allow the XMP Toolkit to cleanly shutdown. @@ -316,6 +317,12 @@ namespace Exiv2 { */ static bool registerNs(const std::string& ns, const std::string& prefix); + /*! + @brief Delete a namespace from the XMP Toolkit. + + XmpProperties::unregisterNs calls this to synchronize namespaces. + */ + static void unregisterNs(const std::string& ns); // DATA static bool initialized_; //! Indicates if the XMP Toolkit has been initialized diff --git a/test/data/cmdxmp.txt b/test/data/cmdxmp.txt index 75bfa982..313f8ac5 100644 --- a/test/data/cmdxmp.txt +++ b/test/data/cmdxmp.txt @@ -54,6 +54,9 @@ set Xmp.tiff.ImageDescription lang=de-DE TIFF Bildbeschreibung # Register a namespace which Exiv2 doesn't know yet with a prefix. reg ns myNamespace/ +# Add a property in the new custom namespace. +set Xmp.ns.myProperty myValue + # There are no built-in Exiv2 value types for structures, qualifiers and # nested types. However, these can be added by using an XmpText value and a # path as the key. @@ -65,7 +68,7 @@ set Xmp.xmpDM.videoFrameSize/stDim:unit inch # Add an element with a qualifier (using the namespace registered earlier) set Xmp.dc.publisher James Bond -set Xmp.dc.publisher/?ns:role secret agent +set Xmp.dc.publisher[1]/?ns:role secret agent # Add a qualifer to an array element of Xmp.dc.creator (added above) set Xmp.dc.creator[2]/?ns:role programmer diff --git a/test/data/xmpparser-test.out b/test/data/xmpparser-test.out index c22762f8..d9f128cd 100644 --- a/test/data/xmpparser-test.out +++ b/test/data/xmpparser-test.out @@ -293,6 +293,7 @@ Xmp.dc.format XmpText 10 image/jpeg Xmp.dc.creator XmpSeq 3 1) The first creator, 2) The second creator, 3) And another one Xmp.dc.description LangAlt 2 lang="x-default" Hello, World, lang="de-DE" Hallo, Welt Xmp.tiff.ImageDescription LangAlt 2 lang="x-default" TIFF image description, lang="de-DE" TIFF Bildbeschreibung +Xmp.ns.myProperty XmpText 7 myValue Xmp.xmpDM.videoFrameSize/stDim:w XmpText 2 16 Xmp.xmpDM.videoFrameSize/stDim:h XmpText 1 9 Xmp.xmpDM.videoFrameSize/stDim:unit XmpText 4 inch @@ -323,7 +324,8 @@ Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man dc:five="256" dc:six="false" dc:seven="Seven" - dc:format="image/jpeg"> + dc:format="image/jpeg" + ns:myProperty="myValue"> Palmtree @@ -427,11 +429,12 @@ Set Xmp.dc.description "Hello, World" (LangAlt) Set Xmp.tiff.ImageDescription "TIFF image description" (LangAlt) Set Xmp.tiff.ImageDescription "lang=de-DE TIFF Bildbeschreibung" (LangAlt) Reg ns="myNamespace/" +Set Xmp.ns.myProperty "myValue" (XmpText) Set Xmp.xmpDM.videoFrameSize/stDim:w "16" (XmpText) Set Xmp.xmpDM.videoFrameSize/stDim:h "9" (XmpText) Set Xmp.xmpDM.videoFrameSize/stDim:unit "inch" (XmpText) Set Xmp.dc.publisher "James Bond" (XmpBag) -Set Xmp.dc.publisher/?ns:role "secret agent" (XmpText) +Set Xmp.dc.publisher[1]/?ns:role "secret agent" (XmpText) Set Xmp.dc.creator[2]/?ns:role "programmer" (XmpText) Set Xmp.xmpBJ.JobRef "type=Bag" (XmpText) Set Xmp.xmpBJ.JobRef[1]/stJob:name "Birthday party" (XmpText) @@ -457,8 +460,9 @@ Xmp.dc.creator[2]/?ns:role XmpText 10 programmer Xmp.dc.creator[3] XmpText 18 3) And another one Xmp.dc.description LangAlt 2 lang="x-default" Hello, World, lang="de-DE" Hallo, Welt Xmp.dc.publisher XmpText 0 type="Bag" -Xmp.dc.publisher/?ns:role XmpText 12 secret agent Xmp.dc.publisher[1] XmpText 10 James Bond +Xmp.dc.publisher[1]/?ns:role XmpText 12 secret agent +Xmp.ns.myProperty XmpText 7 myValue Xmp.tiff.ImageDescription LangAlt 2 lang="x-default" TIFF image description, lang="de-DE" TIFF Bildbeschreibung Xmp.xmpDM.videoFrameSize XmpText 0 type="Struct" Xmp.xmpDM.videoFrameSize/stDim:w XmpText 2 16