Improved XMP value toLong, toFloat and toRational using new functions parseLong, parseFloat and parseRational. (Vladimir Nadvornik)
This commit is contained in:
parent
d8e6b51c69
commit
e878166f0d
@ -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 \
|
||||
|
||||
77
samples/stringto-test.cpp
Normal file
77
samples/stringto-test.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
// ***************************************************************** -*- C++ -*-
|
||||
// stringto-test.cpp, $Rev$
|
||||
// Test conversions from string to long, float and Rational types.
|
||||
|
||||
#include <exiv2/types.hpp>
|
||||
#include <exiv2/error.hpp>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -8,6 +8,14 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
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<float>(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
|
||||
|
||||
@ -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<uint32_t>(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);
|
||||
|
||||
@ -43,6 +43,7 @@ EXIV2_RCSID("@(#) $Id$")
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
@ -349,6 +350,104 @@ namespace Exiv2 {
|
||||
return str;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<>
|
||||
bool stringTo<bool>(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<long>(s, ok);
|
||||
if (ok) return ret;
|
||||
|
||||
float f = stringTo<float>(s, ok);
|
||||
if (ok) return static_cast<long>(f);
|
||||
|
||||
Rational r = stringTo<Rational>(s, ok);
|
||||
if (ok) {
|
||||
if (r.second == 0) {
|
||||
ok = false;
|
||||
return 0;
|
||||
}
|
||||
return static_cast<long>(static_cast<float>(r.first) / r.second);
|
||||
}
|
||||
|
||||
bool b = stringTo<bool>(s, ok);
|
||||
if (ok) return b ? 1 : 0;
|
||||
|
||||
// everything failed, return from stringTo<long> is probably the best fit
|
||||
return ret;
|
||||
}
|
||||
|
||||
float parseFloat(const std::string& s, bool& ok)
|
||||
{
|
||||
float ret = stringTo<float>(s, ok);
|
||||
if (ok) return ret;
|
||||
|
||||
Rational r = stringTo<Rational>(s, ok);
|
||||
if (ok) {
|
||||
if (r.second == 0) {
|
||||
ok = false;
|
||||
return 0.0;
|
||||
}
|
||||
return static_cast<float>(r.first) / r.second;
|
||||
}
|
||||
|
||||
bool b = stringTo<bool>(s, ok);
|
||||
if (ok) return b ? 1.0 : 0.0;
|
||||
|
||||
// everything failed, return from stringTo<float> is probably the best fit
|
||||
return ret;
|
||||
}
|
||||
|
||||
Rational parseRational(const std::string& s, bool& ok)
|
||||
{
|
||||
Rational ret = stringTo<Rational>(s, ok);
|
||||
if (ok) return ret;
|
||||
|
||||
long l = stringTo<long>(s, ok);
|
||||
if (ok) return Rational(l, 1);
|
||||
|
||||
float f = stringTo<float>(s, ok);
|
||||
if (ok) return floatToRationalCast(f);
|
||||
|
||||
bool b = stringTo<bool>(s, ok);
|
||||
if (ok) return b ? Rational(1, 1) : Rational(0, 1);
|
||||
|
||||
// everything failed, return from stringTo<Rational> is probably the best fit
|
||||
return ret;
|
||||
}
|
||||
|
||||
Rational floatToRationalCast(float f)
|
||||
{
|
||||
// Beware: primitive conversion algorithm
|
||||
int32_t den = 1000000;
|
||||
if (std::labs(static_cast<long>(f)) > 2147) den = 10000;
|
||||
if (std::labs(static_cast<long>(f)) > 214748) den = 100;
|
||||
if (std::labs(static_cast<long>(f)) > 21474836) den = 1;
|
||||
const float rnd = f >= 0 ? 0.5 : -0.5;
|
||||
const int32_t nom = static_cast<int32_t>(f * den + rnd);
|
||||
const int32_t g = gcd(nom, den);
|
||||
|
||||
return Rational(nom/g, den/g);
|
||||
}
|
||||
|
||||
} // namespace Exiv2
|
||||
|
||||
#ifdef EXV_ENABLE_NLS
|
||||
|
||||
@ -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 <typename T, int N> 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<typename T>
|
||||
@ -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<typename T>
|
||||
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<bool>(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
|
||||
|
||||
@ -568,17 +568,17 @@ namespace Exiv2 {
|
||||
|
||||
long XmpTextValue::toLong(long /*n*/) const
|
||||
{
|
||||
return stringTo<long>(value_, ok_);
|
||||
return parseLong(value_, ok_);
|
||||
}
|
||||
|
||||
float XmpTextValue::toFloat(long /*n*/) const
|
||||
{
|
||||
return stringTo<float>(value_, ok_);
|
||||
return parseFloat(value_, ok_);
|
||||
}
|
||||
|
||||
Rational XmpTextValue::toRational(long /*n*/) const
|
||||
{
|
||||
return stringTo<Rational>(value_, ok_);
|
||||
return parseRational(value_, ok_);
|
||||
}
|
||||
|
||||
XmpTextValue* XmpTextValue::clone_() const
|
||||
@ -626,17 +626,17 @@ namespace Exiv2 {
|
||||
|
||||
long XmpArrayValue::toLong(long n) const
|
||||
{
|
||||
return stringTo<long>(value_[n], ok_);
|
||||
return parseLong(value_[n], ok_);
|
||||
}
|
||||
|
||||
float XmpArrayValue::toFloat(long n) const
|
||||
{
|
||||
return stringTo<float>(value_[n], ok_);
|
||||
return parseFloat(value_[n], ok_);
|
||||
}
|
||||
|
||||
Rational XmpArrayValue::toRational(long n) const
|
||||
{
|
||||
return stringTo<Rational>(value_[n], ok_);
|
||||
return parseRational(value_[n], ok_);
|
||||
}
|
||||
|
||||
XmpArrayValue* XmpArrayValue::clone_() const
|
||||
|
||||
@ -334,7 +334,7 @@ namespace Exiv2 {
|
||||
|
||||
inline Xmpdatum& Xmpdatum::operator=(const bool& value)
|
||||
{
|
||||
return operator=(value ? "true" : "false");
|
||||
return operator=(value ? "True" : "False");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
||||
@ -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 \
|
||||
|
||||
24
test/data/stringto-test.out
Normal file
24
test/data/stringto-test.out
Normal file
@ -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
|
||||
@ -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">
|
||||
|
||||
24
test/stringto-test.sh
Executable file
24
test/stringto-test.sh
Executable file
@ -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
|
||||
Loading…
Reference in New Issue
Block a user