// SPDX-License-Identifier: GPL-2.0-or-later // included header files #include "value.hpp" #include "convert.hpp" #include "enforce.hpp" #include "error.hpp" #include "types.hpp" // + standard includes #include #include // ***************************************************************************** // class member definitions namespace Exiv2 { Value::Value(TypeId typeId) : type_(typeId) { } Value::UniquePtr Value::create(TypeId typeId) { switch (typeId) { case invalidTypeId: case signedByte: case unsignedByte: return std::make_unique(typeId); case asciiString: return std::make_unique(); case unsignedShort: return std::make_unique>(); case unsignedLong: case tiffIfd: return std::make_unique>(typeId); case unsignedRational: return std::make_unique>(); case undefined: return std::make_unique(); case signedShort: return std::make_unique>(); case signedLong: return std::make_unique>(); case signedRational: return std::make_unique>(); case tiffFloat: return std::make_unique>(); case tiffDouble: return std::make_unique>(); case string: return std::make_unique(); case date: return std::make_unique(); case time: return std::make_unique(); case comment: return std::make_unique(); case xmpText: return std::make_unique(); case xmpBag: case xmpSeq: case xmpAlt: return std::make_unique(typeId); case langAlt: return std::make_unique(); default: return std::make_unique(typeId); } } // Value::create int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) { return -1; } std::string Value::toString() const { std::ostringstream os; write(os); ok_ = !os.fail(); return os.str(); } std::string Value::toString(size_t /*n*/) const { return toString(); } size_t Value::sizeDataArea() const { return 0; } DataBuf Value::dataArea() const { return {nullptr, 0}; } DataValue::DataValue(TypeId typeId) : Value(typeId) { } DataValue::DataValue(const byte* buf, size_t len, ByteOrder byteOrder, TypeId typeId) : Value(typeId) { read(buf, len, byteOrder); } size_t DataValue::count() const { return size(); } int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { // byteOrder not needed value_.assign(buf, buf + len); return 0; } int DataValue::read(const std::string& buf) { std::istringstream is(buf); int tmp = 0; ValueType val; while (!(is.eof())) { is >> tmp; if (is.fail()) return 1; val.push_back(static_cast(tmp)); } value_.swap(val); return 0; } size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { // byteOrder not needed return std::copy(value_.begin(), value_.end(), buf) - buf; } size_t DataValue::size() const { return value_.size(); } DataValue* DataValue::clone_() const { return new DataValue(*this); } std::ostream& DataValue::write(std::ostream& os) const { size_t end = value_.size(); for (size_t i = 0; i != end; ++i) { os << static_cast(value_.at(i)); if (i < end - 1) os << " "; } return os; } std::string DataValue::toString(size_t n) const { std::ostringstream os; os << static_cast(value_.at(n)); ok_ = !os.fail(); return os.str(); } int64_t DataValue::toInt64(size_t n) const { ok_ = true; return value_.at(n); } uint32_t DataValue::toUint32(size_t n) const { ok_ = true; return value_.at(n); } float DataValue::toFloat(size_t n) const { ok_ = true; return value_.at(n); } Rational DataValue::toRational(size_t n) const { ok_ = true; return {value_.at(n), 1}; } StringValueBase::StringValueBase(TypeId typeId) : Value(typeId) { } StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) { read(buf); } StringValueBase& StringValueBase::operator=(const StringValueBase& rhs) { if (this == &rhs) return *this; Value::operator=(rhs); value_ = rhs.value_; return *this; } int StringValueBase::read(const std::string& buf) { value_ = buf; return 0; } int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { // byteOrder not needed if (buf) value_ = std::string(reinterpret_cast(buf), len); return 0; } size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const { if (value_.empty()) return 0; // byteOrder not needed return value_.copy(reinterpret_cast(buf), value_.size()); } size_t StringValueBase::count() const { return size(); } size_t StringValueBase::size() const { return value_.size(); } std::ostream& StringValueBase::write(std::ostream& os) const { return os << value_; } int64_t StringValueBase::toInt64(size_t n) const { ok_ = true; return value_.at(n); } uint32_t StringValueBase::toUint32(size_t n) const { ok_ = true; return value_.at(n); } float StringValueBase::toFloat(size_t n) const { ok_ = true; return value_.at(n); } Rational StringValueBase::toRational(size_t n) const { ok_ = true; return {value_.at(n), 1}; } StringValue::StringValue() : StringValueBase(string) { } StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) { } StringValue* StringValue::clone_() const { return new StringValue(*this); } AsciiValue::AsciiValue() : StringValueBase(asciiString) { } AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) { } int AsciiValue::read(const std::string& buf) { value_ = buf; // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484 if (value_.empty() || value_.at(value_.size() - 1) != '\0') { value_ += '\0'; } return 0; } AsciiValue* AsciiValue::clone_() const { return new AsciiValue(*this); } std::ostream& AsciiValue::write(std::ostream& os) const { // Write only up to the first '\0' (if any) std::string::size_type pos = value_.find_first_of('\0'); if (pos == std::string::npos) pos = value_.size(); return os << value_.substr(0, pos); } constexpr CommentValue::CharsetTable::CharsetTable(CharsetId charsetId, const char* name, const char* code) : charsetId_(charsetId), name_(name), code_(code) { } //! Lookup list of supported IFD type information constexpr CommentValue::CharsetTable CommentValue::CharsetInfo::charsetTable_[] = { CharsetTable(ascii, "Ascii", "ASCII\0\0\0"), CharsetTable(jis, "Jis", "JIS\0\0\0\0\0"), CharsetTable(unicode, "Unicode", "UNICODE\0"), CharsetTable(undefined, "Undefined", "\0\0\0\0\0\0\0\0"), CharsetTable(invalidCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"), CharsetTable(lastCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"), }; const char* CommentValue::CharsetInfo::name(CharsetId charsetId) { return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_; } const char* CommentValue::CharsetInfo::code(CharsetId charsetId) { return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].code_; } CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByName(const std::string& name) { int i = 0; for (; charsetTable_[i].charsetId_ != lastCharsetId && charsetTable_[i].name_ != name; ++i) { } return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; } CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) { int i = 0; for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) { } return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; } CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) { } CommentValue::CommentValue(const std::string& comment) : StringValueBase(Exiv2::undefined) { read(comment); } int CommentValue::read(const std::string& comment) { std::string c = comment; CharsetId charsetId = undefined; if (comment.length() > 8 && comment.substr(0, 8) == "charset=") { const std::string::size_type pos = comment.find_first_of(' '); std::string name = comment.substr(8, pos - 8); // Strip quotes (so you can also specify the charset without quotes) if (!name.empty() && name.front() == '"') name = name.substr(1); if (!name.empty() && name[name.length() - 1] == '"') name = name.substr(0, name.length() - 1); charsetId = CharsetInfo::charsetIdByName(name); if (charsetId == invalidCharsetId) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << Error(ErrorCode::kerInvalidCharset, name) << "\n"; #endif return 1; } c.clear(); if (pos != std::string::npos) c = comment.substr(pos + 1); } if (charsetId == unicode) { const char* to = byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; convertStringCharset(c, "UTF-8", to); } const std::string code(CharsetInfo::code(charsetId), 8); return StringValueBase::read(code + c); } int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) { byteOrder_ = byteOrder; return StringValueBase::read(buf, len, byteOrder); } size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const { std::string c = value_; if (charsetId() == unicode) { c = value_.substr(8); [[maybe_unused]] const size_t sz = c.size(); if (byteOrder_ == littleEndian && byteOrder == bigEndian) { convertStringCharset(c, "UCS-2LE", "UCS-2BE"); } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) { convertStringCharset(c, "UCS-2BE", "UCS-2LE"); } c = value_.substr(0, 8) + c; } if (c.empty()) return 0; return c.copy(reinterpret_cast(buf), c.size()); } std::ostream& CommentValue::write(std::ostream& os) const { CharsetId csId = charsetId(); std::string text = comment(); if (csId != undefined) { os << "charset=" << CharsetInfo::name(csId) << " "; } return os << text; } std::string CommentValue::comment(const char* encoding) const { std::string c; if (value_.length() < 8) { return c; } c = value_.substr(8); if (charsetId() == unicode) { const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding; if (!convertStringCharset(c, from, "UTF-8")) throw Error(ErrorCode::kerInvalidIconvEncoding, encoding, "UTF-8"); } bool bAscii = charsetId() == undefined || charsetId() == ascii; // # 1266 Remove trailing nulls if (bAscii && c.find('\0') != std::string::npos) { c = c.substr(0, c.find('\0')); } return c; } CommentValue::CharsetId CommentValue::charsetId() const { CharsetId charsetId = undefined; if (value_.length() >= 8) { const std::string code = value_.substr(0, 8); charsetId = CharsetInfo::charsetIdByCode(code); } return charsetId; } const char* CommentValue::detectCharset(std::string& c) const { // Interpret a BOM if there is one if (0 == strncmp(c.data(), "\xef\xbb\xbf", 3)) { c = c.substr(3); return "UTF-8"; } if (0 == strncmp(c.data(), "\xff\xfe", 2)) { c = c.substr(2); return "UCS-2LE"; } if (0 == strncmp(c.data(), "\xfe\xff", 2)) { c = c.substr(2); return "UCS-2BE"; } // Todo: Add logic to guess if the comment is encoded in UTF-8 return byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; } CommentValue* CommentValue::clone_() const { return new CommentValue(*this); } XmpValue::XmpValue(TypeId typeId) : Value(typeId) { } void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) { xmpArrayType_ = xmpArrayType; } void XmpValue::setXmpStruct(XmpStruct xmpStruct) { xmpStruct_ = xmpStruct; } XmpValue::XmpArrayType XmpValue::xmpArrayType() const { return xmpArrayType_; } XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) { XmpArrayType xa = xaNone; switch (typeId) { case xmpAlt: xa = xaAlt; break; case xmpBag: xa = xaBag; break; case xmpSeq: xa = xaSeq; break; default: break; } return xa; } XmpValue::XmpStruct XmpValue::xmpStruct() const { return xmpStruct_; } size_t XmpValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { std::ostringstream os; write(os); std::string s = os.str(); if (!s.empty()) std::memcpy(buf, s.data(), s.size()); return s.size(); } int XmpValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { std::string s(reinterpret_cast(buf), len); return read(s); } size_t 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) { // support a type=Alt,Bag,Seq,Struct indicator std::string b = buf; std::string type; if (buf.length() > 5 && buf.substr(0, 5) == "type=") { std::string::size_type pos = buf.find_first_of(' '); type = buf.substr(5, pos - 5); // Strip quotes (so you can also specify the type without quotes) if (!type.empty() && type.front() == '"') type = type.substr(1); if (!type.empty() && type[type.length() - 1] == '"') type = type.substr(0, type.length() - 1); b.clear(); if (pos != std::string::npos) b = buf.substr(pos + 1); } if (!type.empty()) { if (type == "Alt") { setXmpArrayType(XmpValue::xaAlt); } else if (type == "Bag") { setXmpArrayType(XmpValue::xaBag); } else if (type == "Seq") { setXmpArrayType(XmpValue::xaSeq); } else if (type == "Struct") { setXmpStruct(); } else { throw Error(ErrorCode::kerInvalidXmpText, type); } } value_ = b; return 0; } XmpTextValue::UniquePtr XmpTextValue::clone() const { return UniquePtr(clone_()); } size_t XmpTextValue::size() const { return value_.size(); } size_t XmpTextValue::count() const { return size(); } std::ostream& XmpTextValue::write(std::ostream& os) const { bool del = false; if (xmpArrayType() != XmpValue::xaNone) { switch (xmpArrayType()) { case XmpValue::xaAlt: os << "type=\"Alt\""; break; case XmpValue::xaBag: os << "type=\"Bag\""; break; case XmpValue::xaSeq: os << "type=\"Seq\""; break; case XmpValue::xaNone: break; // just to suppress the warning } del = true; } else if (xmpStruct() != XmpValue::xsNone) { switch (xmpStruct()) { case XmpValue::xsStruct: os << "type=\"Struct\""; break; case XmpValue::xsNone: break; // just to suppress the warning } del = true; } if (del && !value_.empty()) os << " "; return os << value_; } int64_t XmpTextValue::toInt64(size_t /*n*/) const { return parseInt64(value_, ok_); } uint32_t XmpTextValue::toUint32(size_t /*n*/) const { return parseUint32(value_, ok_); } float XmpTextValue::toFloat(size_t /*n*/) const { return parseFloat(value_, ok_); } Rational XmpTextValue::toRational(size_t /*n*/) const { return parseRational(value_, ok_); } XmpTextValue* XmpTextValue::clone_() const { return new XmpTextValue(*this); } XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) { setXmpArrayType(xmpArrayType(typeId)); } int XmpArrayValue::read(const std::string& buf) { if (!buf.empty()) value_.push_back(buf); return 0; } XmpArrayValue::UniquePtr XmpArrayValue::clone() const { return UniquePtr(clone_()); } size_t XmpArrayValue::count() const { return value_.size(); } std::ostream& XmpArrayValue::write(std::ostream& os) const { for (auto i = value_.begin(); i != value_.end(); ++i) { if (i != value_.begin()) os << ", "; os << *i; } return os; } std::string XmpArrayValue::toString(size_t n) const { ok_ = true; return value_.at(n); } int64_t XmpArrayValue::toInt64(size_t n) const { return parseInt64(value_.at(n), ok_); } uint32_t XmpArrayValue::toUint32(size_t n) const { return parseUint32(value_.at(n), ok_); } float XmpArrayValue::toFloat(size_t n) const { return parseFloat(value_.at(n), ok_); } Rational XmpArrayValue::toRational(size_t n) const { return parseRational(value_.at(n), ok_); } XmpArrayValue* XmpArrayValue::clone_() const { 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=") { static constexpr auto ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const std::string::size_type pos = buf.find_first_of(' '); if (pos == std::string::npos) { lang = buf.substr(5); } else { lang = buf.substr(5, pos - 5); } if (lang.empty()) throw Error(ErrorCode::kerInvalidLangAltValue, buf); // Strip quotes (so you can also specify the language without quotes) if (lang.front() == '"') { lang = lang.substr(1); if (lang.empty() || lang.find('"') != lang.length() - 1) throw Error(ErrorCode::kerInvalidLangAltValue, buf); lang = lang.substr(0, lang.length() - 1); } if (lang.empty()) throw Error(ErrorCode::kerInvalidLangAltValue, buf); // Check language is in the correct format (see https://www.ietf.org/rfc/rfc3066.txt) std::string::size_type charPos = lang.find_first_not_of(ALPHA); if (charPos != std::string::npos) { static constexpr auto ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; if (lang.at(charPos) != '-' || lang.find_first_not_of(ALPHA_NUM, charPos + 1) != std::string::npos) throw Error(ErrorCode::kerInvalidLangAltValue, buf); } b.clear(); if (pos != std::string::npos) b = buf.substr(pos + 1); } value_[lang] = b; return 0; } LangAltValue::UniquePtr LangAltValue::clone() const { return UniquePtr(clone_()); } size_t LangAltValue::count() const { return value_.size(); } std::ostream& LangAltValue::write(std::ostream& os) const { bool first = true; // Write the default entry first auto i = value_.find("x-default"); if (i != value_.end()) { os << "lang=\"" << i->first << "\" " << i->second; first = false; } // Write the others for (const auto& [lang, s] : value_) { if (lang != "x-default") { if (!first) os << ", "; os << "lang=\"" << lang << "\" " << s; first = false; } } return os; } std::string LangAltValue::toString(size_t /*n*/) const { return toString("x-default"); } std::string LangAltValue::toString(const std::string& qualifier) const { auto i = value_.find(qualifier); if (i != value_.end()) { ok_ = true; return i->second; } ok_ = false; return ""; } int64_t LangAltValue::toInt64(size_t /*n*/) const { ok_ = false; return 0; } uint32_t LangAltValue::toUint32(size_t /*n*/) const { ok_ = false; return 0; } float LangAltValue::toFloat(size_t /*n*/) const { ok_ = false; return 0.0F; } Rational LangAltValue::toRational(size_t /*n*/) const { ok_ = false; return {0, 0}; } LangAltValue* LangAltValue::clone_() const { return new LangAltValue(*this); } DateValue::DateValue() : Value(date) { } DateValue::DateValue(int32_t year, int32_t month, int32_t day) : Value(date) { date_.year = year; date_.month = month; date_.day = day; } int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { const std::string str(reinterpret_cast(buf), len); return read(str); } int DateValue::read(const std::string& buf) { // ISO 8601 date formats: // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf static const std::regex reExtended(R"(^(\d{4})-(\d{2})-(\d{2}))"); static const std::regex reBasic(R"(^(\d{4})(\d{2})(\d{2}))"); std::smatch sm; auto printWarning = [] { #ifndef SUPPRESS_WARNINGS EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n"; #endif }; // Note: We use here regex_search instead of regex_match, because the string can be longer than expected and // also contain the time if (std::regex_search(buf, sm, reExtended) || std::regex_search(buf, sm, reBasic)) { date_.year = std::stoi(sm[1].str()); date_.month = std::stoi(sm[2].str()); if (date_.month > 12) { date_.month = 0; printWarning(); return 1; } date_.day = std::stoi(sm[3].str()); if (date_.day > 31) { date_.day = 0; printWarning(); return 1; } return 0; } printWarning(); return 1; } void DateValue::setDate(const Date& src) { date_.year = src.year; date_.month = src.month; date_.day = src.day; } size_t DateValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { // \note Here the date is copied in the Basic format YYYYMMDD, as the IPTC key Iptc.Application2.DateCreated // wants it. Check https://exiv2.org/iptc.html // sprintf wants to add the null terminator, so use oversized buffer char temp[9]; auto wrote = static_cast(snprintf(temp, sizeof(temp), "%04d%02d%02d", date_.year, date_.month, date_.day)); std::memcpy(buf, temp, wrote); return wrote; } const DateValue::Date& DateValue::getDate() const { return date_; } size_t DateValue::count() const { return size(); } size_t DateValue::size() const { return 8; } DateValue* DateValue::clone_() const { return new DateValue(*this); } std::ostream& DateValue::write(std::ostream& os) const { // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD std::ios::fmtflags f(os.flags()); os << std::setw(4) << std::setfill('0') << date_.year << '-' << std::right << std::setw(2) << std::setfill('0') << date_.month << '-' << std::setw(2) << std::setfill('0') << date_.day; os.flags(f); return os; } int64_t DateValue::toInt64(size_t /*n*/) const { // Range of tm struct is limited to about 1970 to 2038 // This will return -1 if outside that range std::tm tms = {}; tms.tm_mday = date_.day; tms.tm_mon = date_.month - 1; tms.tm_year = date_.year - 1900; auto l = static_cast(std::mktime(&tms)); ok_ = (l != -1); return l; } uint32_t DateValue::toUint32(size_t /*n*/) const { const int64_t t = toInt64(); if (t < 0 || t > std::numeric_limits::max()) { return 0; } return static_cast(t); } float DateValue::toFloat(size_t n) const { return static_cast(toInt64(n)); } Rational DateValue::toRational(size_t n) const { return {static_cast(toInt64(n)), 1}; } TimeValue::TimeValue() : Value(time) { } TimeValue::TimeValue(int32_t hour, int32_t minute, int32_t second, int32_t tzHour, int32_t tzMinute) : Value(date) { time_.hour = hour; time_.minute = minute; time_.second = second; time_.tzHour = tzHour; time_.tzMinute = tzMinute; } int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { const std::string str(reinterpret_cast(buf), len); return read(str); } int TimeValue::read(const std::string& buf) { // ISO 8601 time formats: // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf // Not supported formats: // 4.2.2.4 Representations with decimal fraction: 232050,5 static const std::regex re(R"(^(2[0-3]|[01][0-9]):?([0-5][0-9])?:?([0-5][0-9])?$)"); static const std::regex reExt( R"(^(2[0-3]|[01][0-9]):?([0-5][0-9]):?([0-5][0-9])(Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?)$)"); std::smatch sm; if (std::regex_match(buf, sm, re) || std::regex_match(buf, sm, reExt)) { time_.hour = sm.length(1) ? std::stoi(sm[1].str()) : 0; time_.minute = sm.length(2) ? std::stoi(sm[2].str()) : 0; time_.second = sm.length(3) ? std::stoi(sm[3].str()) : 0; if (sm.size() > 4) { std::string str = sm[4].str(); const auto strSize = str.size(); auto posColon = str.find(':'); if (posColon == std::string::npos) { // Extended format time_.tzHour = std::stoi(str.substr(0, 3)); if (strSize > 3) { int minute = std::stoi(str.substr(3)); time_.tzMinute = time_.tzHour < 0 ? -minute : minute; } } else { // Basic format time_.tzHour = std::stoi(str.substr(0, posColon)); int minute = std::stoi(str.substr(posColon + 1)); time_.tzMinute = time_.tzHour < 0 ? -minute : minute; } } return 0; } #ifndef SUPPRESS_WARNINGS EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n"; #endif return 1; } /// \todo not used internally. At least we should test it void TimeValue::setTime(const Time& src) { std::memcpy(&time_, &src, sizeof(time_)); } size_t TimeValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { // NOTE: Here the time is copied in the Basic format HHMMSS:HHMM, as the IPTC key // Iptc.Application2.TimeCreated wants it. Check https://exiv2.org/iptc.html char temp[12]; char plusMinus = '+'; if (time_.tzHour < 0 || time_.tzMinute < 0) plusMinus = '-'; const auto wrote = static_cast(snprintf(temp, sizeof(temp), // 11 bytes are written + \0 "%02d%02d%02d%1c%02d%02d", time_.hour, time_.minute, time_.second, plusMinus, abs(time_.tzHour), abs(time_.tzMinute))); enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat); std::memcpy(buf, temp, wrote); return wrote; } const TimeValue::Time& TimeValue::getTime() const { return time_; } size_t TimeValue::count() const { return size(); } size_t TimeValue::size() const { return 11; } TimeValue* TimeValue::clone_() const { return new TimeValue(*this); } std::ostream& TimeValue::write(std::ostream& os) const { // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm char plusMinus = '+'; if (time_.tzHour < 0 || time_.tzMinute < 0) plusMinus = '-'; std::ios::fmtflags f(os.flags()); os << std::right << std::setw(2) << std::setfill('0') << time_.hour << ':' << std::setw(2) << std::setfill('0') << time_.minute << ':' << std::setw(2) << std::setfill('0') << time_.second << plusMinus << std::setw(2) << std::setfill('0') << abs(time_.tzHour) << ':' << std::setw(2) << std::setfill('0') << abs(time_.tzMinute); os.flags(f); return os; } int64_t TimeValue::toInt64(size_t /*n*/) const { // Returns number of seconds in the day in UTC. int64_t result = (time_.hour - time_.tzHour) * 60 * 60; result += (time_.minute - time_.tzMinute) * 60; result += time_.second; if (result < 0) { result += 86400; } ok_ = true; return result; } uint32_t TimeValue::toUint32(size_t /*n*/) const { const int64_t t = toInt64(); if (t < 0 || t > std::numeric_limits::max()) { return 0; } return static_cast(t); } float TimeValue::toFloat(size_t n) const { return static_cast(toInt64(n)); } Rational TimeValue::toRational(size_t n) const { return {static_cast(toInt64(n)), 1}; } } // namespace Exiv2