diff --git a/samples/Makefile b/samples/Makefile index 9a990e1c..b36b2c10 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -69,6 +69,7 @@ BINSRC = addmoddel.cpp \ key-test.cpp \ largeiptc-test.cpp \ makernote-test.cpp \ + stringto-test.cpp \ write-test.cpp \ write2-test.cpp \ tiffparse.cpp \ diff --git a/samples/stringto-test.cpp b/samples/stringto-test.cpp new file mode 100644 index 00000000..973dcfcb --- /dev/null +++ b/samples/stringto-test.cpp @@ -0,0 +1,77 @@ +// ***************************************************************** -*- C++ -*- +// stringto-test.cpp, $Rev$ +// Test conversions from string to long, float and Rational types. + +#include +#include +#include +#include + +const char* testcases[] = { + // bool + "True", + "False", + "t", + "f", + // long + "-1", + "0", + "1", + // float + "0.0", + "0.1", + "0.01", + "0.001", + "-1.49999", + "-1.5", + "1.49999", + "1.5", + // Rational + "0/1", + "1/1", + "1/3", + "-1/3", + "4/3", + "-4/3", + "0/0", + // nok + "text" +}; + +int main() +{ + std::cout << std::setfill(' '); + + std::cout << std::setw(12) << std::left << "string"; + std::cout << std::setw(12) << std::left << "long"; + std::cout << std::setw(12) << std::left << "float"; + std::cout << std::setw(12) << std::left << "Rational"; + + std::cout << std::endl; + + for (unsigned int i = 0; i < EXV_COUNTOF(testcases); ++i) try { + std::string s(testcases[i]); + std::cout << std::setw(12) << std::left << s; + bool ok; + + long l = Exiv2::parseLong(s, ok); + std::cout << std::setw(12) << std::left; + if (ok) std::cout << l; else std::cout << "nok"; + + float f = Exiv2::parseFloat(s, ok); + std::cout << std::setw(12) << std::left; + if (ok) std::cout << f; else std::cout << "nok"; + + Exiv2::Rational r = Exiv2::parseRational(s, ok); + if (ok) std::cout << r.first << "/" << r.second; + else std::cout << "nok"; + + std::cout << std::endl; + } + catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; + } + + return 0; +} diff --git a/samples/xmpsample.cpp b/samples/xmpsample.cpp index b8416dcb..76541346 100644 --- a/samples/xmpsample.cpp +++ b/samples/xmpsample.cpp @@ -8,6 +8,14 @@ #include #include #include +#include +#include + +bool isEqual(float a, float b) +{ + double d = std::fabs(a - b); + return d < 0.00001; +} int main() try { @@ -39,6 +47,59 @@ try { // In addition, there is a dedicated assignment operator for Exiv2::Value Exiv2::XmpTextValue val("Seven"); xmpData["Xmp.dc.seven"] = val; + xmpData["Xmp.dc.eight"] = true; + + // Extracting values + assert(xmpData["Xmp.dc.one"].toLong() == -1); + assert(xmpData["Xmp.dc.one"].value().ok()); + + const Exiv2::Value &getv1 = xmpData["Xmp.dc.one"].value(); + assert(isEqual(getv1.toFloat(), -1)); + assert(getv1.ok()); + assert(getv1.toRational() == Exiv2::Rational(-1, 1)); + assert(getv1.ok()); + + const Exiv2::Value &getv2 = xmpData["Xmp.dc.two"].value(); + assert(isEqual(getv2.toFloat(), 3.1415)); + assert(getv2.ok()); + assert(getv2.toLong() == 3); + assert(getv2.ok()); + Exiv2::Rational R = getv2.toRational(); + assert(getv2.ok()); + assert(isEqual(static_cast(R.first) / R.second, 3.1415 )); + + const Exiv2::Value &getv3 = xmpData["Xmp.dc.three"].value(); + assert(isEqual(getv3.toFloat(), 5.0/7.0)); + assert(getv3.ok()); + assert(getv3.toLong() == 0); // long(5.0 / 7.0) + assert(getv3.ok()); + assert(getv3.toRational() == Exiv2::Rational(5, 7)); + assert(getv3.ok()); + + const Exiv2::Value &getv6 = xmpData["Xmp.dc.six"].value(); + assert(getv6.toLong() == 0); + assert(getv6.ok()); + assert(getv6.toFloat() == 0.0); + assert(getv6.ok()); + assert(getv6.toRational() == Exiv2::Rational(0, 1)); + assert(getv6.ok()); + + const Exiv2::Value &getv7 = xmpData["Xmp.dc.seven"].value(); + getv7.toLong(); // this should fail + assert(!getv7.ok()); + + const Exiv2::Value &getv8 = xmpData["Xmp.dc.eight"].value(); + assert(getv8.toLong() == 1); + assert(getv8.ok()); + assert(getv8.toFloat() == 1.0); + assert(getv8.ok()); + assert(getv8.toRational() == Exiv2::Rational(1, 1)); + assert(getv8.ok()); + + // Deleting an XMP property + Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey("Xmp.dc.eight")); + if (pos == xmpData.end()) throw Exiv2::Error(1, "Key not found"); + xmpData.erase(pos); // ------------------------------------------------------------------------- // Exiv2 has specialized values for simple XMP properties, arrays of simple diff --git a/src/crwimage.cpp b/src/crwimage.cpp index b2a53cbb..8808756b 100644 --- a/src/crwimage.cpp +++ b/src/crwimage.cpp @@ -960,11 +960,7 @@ namespace Exiv2 { if (ifdId == canonSiIfdId) { // Exif.Photo.FNumber float f = fnumber(canonEv(aperture)); - // Beware: primitive conversion algorithm - uint32_t den = 1000000; - uint32_t nom = static_cast(f * den); - uint32_t g = gcd(nom, den); - URational ur(nom/g, den/g); + URational ur = floatToRationalCast(f); URationalValue fn; fn.value_.push_back(ur); image.exifData().add(ExifKey("Exif.Photo.FNumber"), &fn); diff --git a/src/types.cpp b/src/types.cpp index b3f5d3af..b162fd76 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -43,6 +43,7 @@ EXIV2_RCSID("@(#) $Id$") #include #include #include +#include #include #include @@ -349,6 +350,104 @@ namespace Exiv2 { return str; #endif } + + template<> + bool stringTo(const std::string& s, bool& ok) + { + std::string lcs(s); /* lowercase string */ + for(unsigned i = 0; i < lcs.length(); i++) { + lcs[i] = std::tolower(s[i]); + } + /* handle the same values as xmp sdk */ + if (lcs == "false" || lcs == "f" || lcs == "0") { + ok = true; + return false; + } + if (lcs == "true" || lcs == "t" || lcs == "1") { + ok = true; + return true; + } + ok = false; + return false; + } + + long parseLong(const std::string& s, bool& ok) + { + long ret = stringTo(s, ok); + if (ok) return ret; + + float f = stringTo(s, ok); + if (ok) return static_cast(f); + + Rational r = stringTo(s, ok); + if (ok) { + if (r.second == 0) { + ok = false; + return 0; + } + return static_cast(static_cast(r.first) / r.second); + } + + bool b = stringTo(s, ok); + if (ok) return b ? 1 : 0; + + // everything failed, return from stringTo is probably the best fit + return ret; + } + + float parseFloat(const std::string& s, bool& ok) + { + float ret = stringTo(s, ok); + if (ok) return ret; + + Rational r = stringTo(s, ok); + if (ok) { + if (r.second == 0) { + ok = false; + return 0.0; + } + return static_cast(r.first) / r.second; + } + + bool b = stringTo(s, ok); + if (ok) return b ? 1.0 : 0.0; + + // everything failed, return from stringTo is probably the best fit + return ret; + } + + Rational parseRational(const std::string& s, bool& ok) + { + Rational ret = stringTo(s, ok); + if (ok) return ret; + + long l = stringTo(s, ok); + if (ok) return Rational(l, 1); + + float f = stringTo(s, ok); + if (ok) return floatToRationalCast(f); + + bool b = stringTo(s, ok); + if (ok) return b ? Rational(1, 1) : Rational(0, 1); + + // everything failed, return from stringTo is probably the best fit + return ret; + } + + Rational floatToRationalCast(float f) + { + // Beware: primitive conversion algorithm + int32_t den = 1000000; + if (std::labs(static_cast(f)) > 2147) den = 10000; + if (std::labs(static_cast(f)) > 214748) den = 100; + if (std::labs(static_cast(f)) > 21474836) den = 1; + const float rnd = f >= 0 ? 0.5 : -0.5; + const int32_t nom = static_cast(f * den + rnd); + const int32_t g = gcd(nom, den); + + return Rational(nom/g, den/g); + } + } // namespace Exiv2 #ifdef EXV_ENABLE_NLS diff --git a/src/types.hpp b/src/types.hpp index 3664e64d..40407f21 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -321,6 +321,58 @@ namespace Exiv2 { */ const char* exvGettext(const char* str); + /*! + @brief Return a \em long set to the value represented by \em s. + + Besides strings that represent \em long values, the function also + handles \em float, \em Rational and boolean + (see also: stringTo(const std::string& s, bool& ok)). + + @param s String to parse + @param ok Output variable indicating the success of the operation. + @return Returns the \em long value represented by \em s and sets \em ok + to \c true if the conversion was successful or \c false if not. + */ + long parseLong(const std::string& s, bool& ok); + + /*! + @brief Return a \em float set to the value represented by \em s. + + Besides strings that represent \em float values, the function also + handles \em long, \em Rational and boolean + (see also: stringTo(const std::string& s, bool& ok)). + + @param s String to parse + @param ok Output variable indicating the success of the operation. + @return Returns the \em float value represented by \em s and sets \em ok + to \c true if the conversion was successful or \c false if not. + */ + float parseFloat(const std::string& s, bool& ok); + + /*! + @brief Return a \em Rational set to the value represented by \em s. + + Besides strings that represent \em Rational values, the function also + handles \em long, \em float and boolean + (see also: stringTo(const std::string& s, bool& ok)). + Uses floatToRationalCast(float f) if the string can be parsed into a + \em float. + + @param s String to parse + @param ok Output variable indicating the success of the operation. + @return Returns the \em Rational value represented by \em s and sets \em ok + to \c true if the conversion was successful or \c false if not. + */ + Rational parseRational(const std::string& s, bool& ok); + + /*! + @brief Very simple conversion of a \em float to a \em Rational. + + Test it with the values that you expect and check the implementation + to see if this is really what you want! + */ + Rational floatToRationalCast(float f); + // ***************************************************************************** // template and inline definitions @@ -378,7 +430,7 @@ namespace Exiv2 { //! Template used in the COUNTOF macro to determine the size of an array template char (&sizer(T (&)[N]))[N]; //! Macro to determine the size of an array -#define EXV_COUNTOF(a) (sizeof(sizer(a))) +#define EXV_COUNTOF(a) (sizeof(Exiv2::sizer(a))) //! Utility function to convert the argument of any type to a string template @@ -389,19 +441,42 @@ namespace Exiv2 { return os.str(); } - //! Utility function to convert a string to a value of type T. + /*! + @brief Utility function to convert a string to a value of type \c T. + + The string representation of the value must match that recognized by + the input operator for \c T for this function to succeed. + + @param s String to convert + @param ok Output variable indicating the success of the operation. + @return Returns the converted value and sets \em ok to \c true if the + conversion was successful or \c false if not. + */ template T stringTo(const std::string& s, bool& ok) { std::istringstream is(s); T tmp; ok = is >> tmp ? true : false; + std::string rest; + is >> std::skipws >> rest; + if (!rest.empty()) ok = false; return tmp; } + /*! + @brief Specialization of stringTo(const std::string& s, bool& ok) for \em bool. + + Handles the same string values as the XMP SDK. Converts the string to lowercase + and returns \c true if it is "true", "t" or "1", and \c false if it is + "false", "f" or "0". + */ + template<> + bool stringTo(const std::string& s, bool& ok); + /*! @brief Return the greatest common denominator of n and m. - (implementation from Boost rational.hpp) + (Implementation from Boost rational.hpp) @note We use n and m as temporaries in this function, so there is no value in using const IntType& as we would only need to make a copy diff --git a/src/value.cpp b/src/value.cpp index 2379ef22..96ef9b78 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -568,17 +568,17 @@ namespace Exiv2 { long XmpTextValue::toLong(long /*n*/) const { - return stringTo(value_, ok_); + return parseLong(value_, ok_); } float XmpTextValue::toFloat(long /*n*/) const { - return stringTo(value_, ok_); + return parseFloat(value_, ok_); } Rational XmpTextValue::toRational(long /*n*/) const { - return stringTo(value_, ok_); + return parseRational(value_, ok_); } XmpTextValue* XmpTextValue::clone_() const @@ -626,17 +626,17 @@ namespace Exiv2 { long XmpArrayValue::toLong(long n) const { - return stringTo(value_[n], ok_); + return parseLong(value_[n], ok_); } float XmpArrayValue::toFloat(long n) const { - return stringTo(value_[n], ok_); + return parseFloat(value_[n], ok_); } Rational XmpArrayValue::toRational(long n) const { - return stringTo(value_[n], ok_); + return parseRational(value_[n], ok_); } XmpArrayValue* XmpArrayValue::clone_() const diff --git a/src/xmp.hpp b/src/xmp.hpp index 82857d2f..14c37448 100644 --- a/src/xmp.hpp +++ b/src/xmp.hpp @@ -334,7 +334,7 @@ namespace Exiv2 { inline Xmpdatum& Xmpdatum::operator=(const bool& value) { - return operator=(value ? "true" : "false"); + return operator=(value ? "True" : "False"); } template diff --git a/test/Makefile b/test/Makefile index d26119e1..4db329df 100644 --- a/test/Makefile +++ b/test/Makefile @@ -59,8 +59,8 @@ SHELL = /bin/sh # Add test drivers to this list TESTS = addmoddel.sh bugfixes-test.sh crw-test.sh exifdata-test.sh \ exiv2-test.sh ifd-test.sh imagetest.sh iotest.sh iptctest.sh \ - makernote-test.sh modify-test.sh path-test.sh write-test.sh \ - write2-test.sh xmpparser-test.sh + makernote-test.sh modify-test.sh path-test.sh stringto-test.sh \ + write-test.sh write2-test.sh xmpparser-test.sh test: @list='$(TESTS)'; for p in $$list; do \ diff --git a/test/data/stringto-test.out b/test/data/stringto-test.out new file mode 100644 index 00000000..914782b8 --- /dev/null +++ b/test/data/stringto-test.out @@ -0,0 +1,24 @@ +string long float Rational +True 1 1 1/1 +False 0 0 0/1 +t 1 1 1/1 +f 0 0 0/1 +-1 -1 -1 -1/1 +0 0 0 0/1 +1 1 1 1/1 +0.0 0 0 0/1 +0.1 0 0.1 1/10 +0.01 0 0.01 1/100 +0.001 0 0.001 1/1000 +-1.49999 -1 -1.49999 -149999/100000 +-1.5 -1 -1.5 -3/2 +1.49999 1 1.49999 149999/100000 +1.5 1 1.5 3/2 +0/1 0 0 0/1 +1/1 1 1 1/1 +1/3 0 0.333333 1/3 +-1/3 0 -0.333333 -1/3 +4/3 1 1.33333 4/3 +-4/3 -1 -1.33333 -4/3 +0/0 nok nok 0/0 +text nok nok nok diff --git a/test/data/xmpparser-test.out b/test/data/xmpparser-test.out index d9f128cd..4fe1c652 100644 --- a/test/data/xmpparser-test.out +++ b/test/data/xmpparser-test.out @@ -287,7 +287,7 @@ Xmp.dc.two XmpText 6 3.1415 Xmp.dc.three XmpText 3 5/7 Xmp.dc.four XmpText 3 255 Xmp.dc.five XmpText 3 256 -Xmp.dc.six XmpText 5 false +Xmp.dc.six XmpText 5 False Xmp.dc.seven XmpText 5 Seven Xmp.dc.format XmpText 10 image/jpeg Xmp.dc.creator XmpSeq 3 1) The first creator, 2) The second creator, 3) And another one @@ -322,7 +322,7 @@ Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man dc:three="5/7" dc:four="255" dc:five="256" - dc:six="false" + dc:six="False" dc:seven="Seven" dc:format="image/jpeg" ns:myProperty="myValue"> diff --git a/test/stringto-test.sh b/test/stringto-test.sh new file mode 100755 index 00000000..dabe4b06 --- /dev/null +++ b/test/stringto-test.sh @@ -0,0 +1,24 @@ +#! /bin/sh +# Test driver for tests of stringToLong/Float/Rational +results="./tmp/stringto-test.out" +good="./data/stringto-test.out" +diffargs="--strip-trailing-cr" +tmpfile=tmp/ttt +touch $tmpfile +diff -q $diffargs $tmpfile $tmpfile 2>/dev/null +if [ $? -ne 0 ] ; then + diffargs="" +fi +( +binpath="$VALGRIND ../../samples" +cd ./tmp +$binpath/stringto-test +) > $results + +diff -q $diffargs $results $good +rc=$? +if [ $rc -eq 0 ] ; then + echo "All testcases passed." +else + diff $diffargs $results $good +fi