Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "value.hpp" |
5 | | #include "config.h" |
6 | | #include "convert.hpp" |
7 | | #include "enforce.hpp" |
8 | | #include "error.hpp" |
9 | | #include "image_int.hpp" |
10 | | #include "types.hpp" |
11 | | |
12 | | // + standard includes |
13 | | #include <iterator> |
14 | | #include <sstream> |
15 | | |
16 | | // ***************************************************************************** |
17 | | // class member definitions |
18 | | namespace Exiv2 { |
19 | 70.8M | Value::Value(TypeId typeId) : type_(typeId) { |
20 | 70.8M | } |
21 | | |
22 | 28.8M | Value::UniquePtr Value::create(TypeId typeId) { |
23 | 28.8M | switch (typeId) { |
24 | 0 | case invalidTypeId: |
25 | 11.1k | case signedByte: |
26 | 3.82M | case unsignedByte: |
27 | 3.82M | return std::make_unique<DataValue>(typeId); |
28 | 143k | case asciiString: |
29 | 143k | return std::make_unique<AsciiValue>(); |
30 | 17.0M | case unsignedShort: |
31 | 17.0M | return std::make_unique<ValueType<uint16_t>>(); |
32 | 262k | case unsignedLong: |
33 | 297k | case tiffIfd: |
34 | 297k | return std::make_unique<ValueType<uint32_t>>(typeId); |
35 | 63.1k | case unsignedRational: |
36 | 63.1k | return std::make_unique<ValueType<URational>>(); |
37 | 152k | case undefined: |
38 | 152k | return std::make_unique<DataValue>(); |
39 | 2.98M | case signedShort: |
40 | 2.98M | return std::make_unique<ValueType<int16_t>>(); |
41 | 269k | case signedLong: |
42 | 269k | return std::make_unique<ValueType<int32_t>>(); |
43 | 45.7k | case signedRational: |
44 | 45.7k | return std::make_unique<ValueType<Rational>>(); |
45 | 17.5k | case tiffFloat: |
46 | 17.5k | return std::make_unique<ValueType<float>>(); |
47 | 21.1k | case tiffDouble: |
48 | 21.1k | return std::make_unique<ValueType<double>>(); |
49 | 128k | case string: |
50 | 128k | return std::make_unique<StringValue>(); |
51 | 8.44k | case date: |
52 | 8.44k | return std::make_unique<DateValue>(); |
53 | 15.4k | case time: |
54 | 15.4k | return std::make_unique<TimeValue>(); |
55 | 7.11k | case comment: |
56 | 7.11k | return std::make_unique<CommentValue>(); |
57 | 98.8k | case xmpText: |
58 | 98.8k | return std::make_unique<XmpTextValue>(); |
59 | 0 | case xmpBag: |
60 | 19.8k | case xmpSeq: |
61 | 19.8k | case xmpAlt: |
62 | 19.8k | return std::make_unique<XmpArrayValue>(typeId); |
63 | 0 | case langAlt: |
64 | 0 | return std::make_unique<LangAltValue>(); |
65 | 3.71M | default: |
66 | 3.71M | return std::make_unique<DataValue>(typeId); |
67 | 28.8M | } |
68 | 28.8M | } // Value::create |
69 | | |
70 | 5.97k | int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) { |
71 | 5.97k | return -1; |
72 | 5.97k | } |
73 | | |
74 | 373k | std::string Value::toString() const { |
75 | 373k | std::ostringstream os; |
76 | 373k | write(os); |
77 | 373k | ok_ = !os.fail(); |
78 | 373k | return os.str(); |
79 | 373k | } |
80 | | |
81 | 167k | std::string Value::toString(size_t /*n*/) const { |
82 | 167k | return toString(); |
83 | 167k | } |
84 | | |
85 | 103k | size_t Value::sizeDataArea() const { |
86 | 103k | return 0; |
87 | 103k | } |
88 | | |
89 | 4.37k | DataBuf Value::dataArea() const { |
90 | 4.37k | return {nullptr, 0}; |
91 | 4.37k | } |
92 | | |
93 | 7.68M | DataValue::DataValue(TypeId typeId) : Value(typeId) { |
94 | 7.68M | } |
95 | | |
96 | 17 | DataValue::DataValue(const byte* buf, size_t len, ByteOrder byteOrder, TypeId typeId) : Value(typeId) { |
97 | 17 | read(buf, len, byteOrder); |
98 | 17 | } |
99 | | |
100 | 16.3M | size_t DataValue::count() const { |
101 | 16.3M | return size(); |
102 | 16.3M | } |
103 | | |
104 | 7.68M | int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
105 | | // byteOrder not needed |
106 | 7.68M | value_.assign(buf, buf + len); |
107 | 7.68M | return 0; |
108 | 7.68M | } |
109 | | |
110 | 0 | int DataValue::read(const std::string& buf) { |
111 | 0 | std::istringstream is(buf); |
112 | 0 | int tmp = 0; |
113 | 0 | ValueType val; |
114 | 0 | while (is >> tmp) |
115 | 0 | val.push_back(tmp); |
116 | 0 | if (!is.eof()) |
117 | 0 | return 1; |
118 | 0 | value_ = std::move(val); |
119 | 0 | return 0; |
120 | 0 | } |
121 | | |
122 | 5.72M | size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
123 | | // byteOrder not needed |
124 | 5.72M | return std::copy(value_.begin(), value_.end(), buf) - buf; |
125 | 5.72M | } |
126 | | |
127 | 36.2M | size_t DataValue::size() const { |
128 | 36.2M | return value_.size(); |
129 | 36.2M | } |
130 | | |
131 | 25.4M | DataValue* DataValue::clone_() const { |
132 | 25.4M | return new DataValue(*this); |
133 | 25.4M | } |
134 | | |
135 | 115k | std::ostream& DataValue::write(std::ostream& os) const { |
136 | 115k | if (!value_.empty()) { |
137 | 113k | std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<int>(os, " ")); |
138 | 113k | os << static_cast<int>(value_.back()); |
139 | 113k | } |
140 | 115k | return os; |
141 | 115k | } |
142 | | |
143 | 1.91k | std::string DataValue::toString(size_t n) const { |
144 | 1.91k | ok_ = true; |
145 | 1.91k | return std::to_string(value_.at(n)); |
146 | 1.91k | } |
147 | | |
148 | 396k | int64_t DataValue::toInt64(size_t n) const { |
149 | 396k | ok_ = true; |
150 | 396k | return value_.at(n); |
151 | 396k | } |
152 | | |
153 | 227k | uint32_t DataValue::toUint32(size_t n) const { |
154 | 227k | ok_ = true; |
155 | 227k | return value_.at(n); |
156 | 227k | } |
157 | | |
158 | 1.77k | float DataValue::toFloat(size_t n) const { |
159 | 1.77k | ok_ = true; |
160 | 1.77k | return value_.at(n); |
161 | 1.77k | } |
162 | | |
163 | 2.24k | Rational DataValue::toRational(size_t n) const { |
164 | 2.24k | ok_ = true; |
165 | 2.24k | return {value_.at(n), 1}; |
166 | 2.24k | } |
167 | | |
168 | 0 | StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) { |
169 | 0 | read(buf); |
170 | 0 | } |
171 | | |
172 | 52.7k | int StringValueBase::read(const std::string& buf) { |
173 | 52.7k | value_ = buf; |
174 | 52.7k | return 0; |
175 | 52.7k | } |
176 | | |
177 | 210k | int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
178 | | // byteOrder not needed |
179 | 210k | if (buf) |
180 | 210k | value_ = std::string(reinterpret_cast<const char*>(buf), len); |
181 | 210k | return 0; |
182 | 210k | } |
183 | | |
184 | 152k | size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
185 | 152k | if (value_.empty()) |
186 | 76.2k | return 0; |
187 | | // byteOrder not needed |
188 | 76.1k | return value_.copy(reinterpret_cast<char*>(buf), value_.size()); |
189 | 152k | } |
190 | | |
191 | 430k | size_t StringValueBase::count() const { |
192 | 430k | return size(); |
193 | 430k | } |
194 | | |
195 | 837k | size_t StringValueBase::size() const { |
196 | 837k | return value_.size(); |
197 | 837k | } |
198 | | |
199 | 52.9k | std::ostream& StringValueBase::write(std::ostream& os) const { |
200 | 52.9k | return os << value_; |
201 | 52.9k | } |
202 | | |
203 | 114k | int64_t StringValueBase::toInt64(size_t n) const { |
204 | 114k | ok_ = true; |
205 | 114k | return value_.at(n); |
206 | 114k | } |
207 | | |
208 | 70.4k | uint32_t StringValueBase::toUint32(size_t n) const { |
209 | 70.4k | ok_ = true; |
210 | 70.4k | return value_.at(n); |
211 | 70.4k | } |
212 | | |
213 | 301 | float StringValueBase::toFloat(size_t n) const { |
214 | 301 | ok_ = true; |
215 | 301 | return value_.at(n); |
216 | 301 | } |
217 | | |
218 | 346 | Rational StringValueBase::toRational(size_t n) const { |
219 | 346 | ok_ = true; |
220 | 346 | return {value_.at(n), 1}; |
221 | 346 | } |
222 | | |
223 | 128k | StringValue::StringValue() : StringValueBase(string) { |
224 | 128k | } |
225 | | |
226 | 0 | StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) { |
227 | 0 | } |
228 | | |
229 | 947k | StringValue* StringValue::clone_() const { |
230 | 947k | return new StringValue(*this); |
231 | 947k | } |
232 | | |
233 | 143k | AsciiValue::AsciiValue() : StringValueBase(asciiString) { |
234 | 143k | } |
235 | | |
236 | 0 | AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) { |
237 | 0 | } |
238 | | |
239 | 32.8k | int AsciiValue::read(const std::string& buf) { |
240 | 32.8k | value_ = buf; |
241 | | // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484 |
242 | 32.8k | if (value_.empty() || value_.back() != '\0') { |
243 | 32.8k | value_ += '\0'; |
244 | 32.8k | } |
245 | 32.8k | return 0; |
246 | 32.8k | } |
247 | | |
248 | 385k | AsciiValue* AsciiValue::clone_() const { |
249 | 385k | return new AsciiValue(*this); |
250 | 385k | } |
251 | | |
252 | 171k | std::ostream& AsciiValue::write(std::ostream& os) const { |
253 | | // Write only up to the first '\0' (if any) |
254 | 171k | std::string::size_type pos = value_.find_first_of('\0'); |
255 | 171k | if (pos == std::string::npos) |
256 | 15.0k | pos = value_.size(); |
257 | 171k | return os << value_.substr(0, pos); |
258 | 171k | } |
259 | | |
260 | | //! Lookup list of supported IFD type information |
261 | | constexpr CommentValue::CharsetTable CommentValue::CharsetInfo::charsetTable_[] = { |
262 | | {ascii, "Ascii", "ASCII\0\0\0"}, |
263 | | {jis, "Jis", "JIS\0\0\0\0\0"}, |
264 | | {unicode, "Unicode", "UNICODE\0"}, |
265 | | {undefined, "Undefined", "\0\0\0\0\0\0\0\0"}, |
266 | | {invalidCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"}, |
267 | | {lastCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"}, |
268 | | }; |
269 | | |
270 | 826 | const char* CommentValue::CharsetInfo::name(CharsetId charsetId) { |
271 | 826 | return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_; |
272 | 826 | } |
273 | | |
274 | 0 | const char* CommentValue::CharsetInfo::code(CharsetId charsetId) { |
275 | 0 | return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].code_; |
276 | 0 | } |
277 | | |
278 | 0 | CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByName(const std::string& name) { |
279 | 0 | int i = 0; |
280 | 0 | for (; charsetTable_[i].charsetId_ != lastCharsetId && charsetTable_[i].name_ != name; ++i) { |
281 | 0 | } |
282 | 0 | return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; |
283 | 0 | } |
284 | | |
285 | 7.43k | CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) { |
286 | 7.43k | int i = 0; |
287 | 30.5k | for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) { |
288 | 23.1k | } |
289 | 7.43k | return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_; |
290 | 7.43k | } |
291 | | |
292 | 7.11k | CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) { |
293 | 7.11k | } |
294 | | |
295 | 0 | CommentValue::CommentValue(const std::string& comment) : StringValueBase(Exiv2::undefined) { |
296 | 0 | read(comment); |
297 | 0 | } |
298 | | |
299 | 0 | int CommentValue::read(const std::string& comment) { |
300 | 0 | std::string c = comment; |
301 | 0 | CharsetId charsetId = undefined; |
302 | 0 | if (comment.starts_with("charset=")) { |
303 | 0 | const std::string::size_type pos = comment.find_first_of(' '); |
304 | 0 | std::string name = comment.substr(8, pos - 8); |
305 | | // Strip quotes (so you can also specify the charset without quotes) |
306 | 0 | if (!name.empty() && name.front() == '"') |
307 | 0 | name = name.substr(1); |
308 | 0 | if (!name.empty() && name.back() == '"') |
309 | 0 | name.pop_back(); |
310 | 0 | charsetId = CharsetInfo::charsetIdByName(name); |
311 | 0 | if (charsetId == invalidCharsetId) { |
312 | 0 | #ifndef SUPPRESS_WARNINGS |
313 | 0 | EXV_WARNING << Error(ErrorCode::kerInvalidCharset, name) << "\n"; |
314 | 0 | #endif |
315 | 0 | return 1; |
316 | 0 | } |
317 | 0 | c.clear(); |
318 | 0 | if (pos != std::string::npos) |
319 | 0 | c = comment.substr(pos + 1); |
320 | 0 | } |
321 | 0 | if (charsetId == unicode) { |
322 | 0 | const char* to = byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; |
323 | 0 | convertStringCharset(c, "UTF-8", to); |
324 | 0 | } |
325 | 0 | const std::string code(CharsetInfo::code(charsetId), 8); |
326 | 0 | return StringValueBase::read(code + c); |
327 | 0 | } |
328 | | |
329 | 7.11k | int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) { |
330 | 7.11k | byteOrder_ = byteOrder; |
331 | 7.11k | return StringValueBase::read(buf, len, byteOrder); |
332 | 7.11k | } |
333 | | |
334 | 5.01k | size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const { |
335 | 5.01k | std::string c = value_; |
336 | 5.01k | if (charsetId() == unicode) { |
337 | 1.17k | c = value_.substr(8); |
338 | 1.17k | [[maybe_unused]] const size_t sz = c.size(); |
339 | 1.17k | if (byteOrder_ == littleEndian && byteOrder == bigEndian) { |
340 | 0 | convertStringCharset(c, "UCS-2LE", "UCS-2BE"); |
341 | 1.17k | } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) { |
342 | 338 | convertStringCharset(c, "UCS-2BE", "UCS-2LE"); |
343 | 338 | } |
344 | 1.17k | c = value_.substr(0, 8) + c; |
345 | 1.17k | } |
346 | 5.01k | if (c.empty()) |
347 | 1.36k | return 0; |
348 | 3.64k | return c.copy(reinterpret_cast<char*>(buf), c.size()); |
349 | 5.01k | } |
350 | | |
351 | 1.16k | std::ostream& CommentValue::write(std::ostream& os) const { |
352 | 1.16k | CharsetId csId = charsetId(); |
353 | 1.16k | std::string text = comment(); |
354 | 1.16k | if (csId != undefined) { |
355 | 826 | os << "charset=" << CharsetInfo::name(csId) << " "; |
356 | 826 | } |
357 | 1.16k | return os << text; |
358 | 1.16k | } |
359 | | |
360 | 1.16k | std::string CommentValue::comment(const char* encoding) const { |
361 | 1.16k | std::string c; |
362 | 1.16k | if (value_.length() < 8) { |
363 | 78 | return c; |
364 | 78 | } |
365 | 1.08k | c = value_.substr(8); |
366 | 1.08k | if (charsetId() == unicode) { |
367 | 311 | const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding; |
368 | 311 | if (!convertStringCharset(c, from, "UTF-8")) |
369 | 19 | throw Error(ErrorCode::kerInvalidIconvEncoding, from, "UTF-8"); |
370 | 311 | } |
371 | | |
372 | | // # 1266 Remove trailing nulls |
373 | 1.07k | if (charsetId() == undefined || charsetId() == ascii) { |
374 | 284 | auto n = c.find('\0'); |
375 | 284 | if (n != std::string::npos) |
376 | 186 | c.resize(n); |
377 | 284 | } |
378 | 1.07k | return c; |
379 | 1.08k | } |
380 | | |
381 | 9.16k | CommentValue::CharsetId CommentValue::charsetId() const { |
382 | 9.16k | CharsetId charsetId = undefined; |
383 | 9.16k | if (value_.length() >= 8) { |
384 | 7.43k | const std::string code = value_.substr(0, 8); |
385 | 7.43k | charsetId = CharsetInfo::charsetIdByCode(code); |
386 | 7.43k | } |
387 | 9.16k | return charsetId; |
388 | 9.16k | } |
389 | | |
390 | 311 | const char* CommentValue::detectCharset(std::string& c) const { |
391 | | // Interpret a BOM if there is one |
392 | 311 | if (c.compare(0, 3, "\xef\xbb\xbf") == 0) { |
393 | 48 | c = c.substr(3); |
394 | 48 | return "UTF-8"; |
395 | 48 | } |
396 | 263 | if (c.compare(0, 2, "\xff\xfe") == 0) { |
397 | 30 | c = c.substr(2); |
398 | 30 | return "UCS-2LE"; |
399 | 30 | } |
400 | 233 | if (c.compare(0, 2, "\xfe\xff") == 0) { |
401 | 46 | c = c.substr(2); |
402 | 46 | return "UCS-2BE"; |
403 | 46 | } |
404 | | |
405 | | // Todo: Add logic to guess if the comment is encoded in UTF-8 |
406 | | |
407 | 187 | return byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE"; |
408 | 233 | } |
409 | | |
410 | 18.2k | CommentValue* CommentValue::clone_() const { |
411 | 18.2k | return new CommentValue(*this); |
412 | 18.2k | } |
413 | | |
414 | 31.5k | void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) { |
415 | 31.5k | xmpArrayType_ = xmpArrayType; |
416 | 31.5k | } |
417 | | |
418 | 8.69k | void XmpValue::setXmpStruct(XmpStruct xmpStruct) { |
419 | 8.69k | xmpStruct_ = xmpStruct; |
420 | 8.69k | } |
421 | | |
422 | 473k | XmpValue::XmpArrayType XmpValue::xmpArrayType() const { |
423 | 473k | return xmpArrayType_; |
424 | 473k | } |
425 | | |
426 | 31.1k | XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) { |
427 | 31.1k | XmpArrayType xa = xaNone; |
428 | 31.1k | switch (typeId) { |
429 | 574 | case xmpAlt: |
430 | 574 | xa = xaAlt; |
431 | 574 | break; |
432 | 387 | case xmpBag: |
433 | 387 | xa = xaBag; |
434 | 387 | break; |
435 | 22.3k | case xmpSeq: |
436 | 22.3k | xa = xaSeq; |
437 | 22.3k | break; |
438 | 7.85k | default: |
439 | 7.85k | break; |
440 | 31.1k | } |
441 | 31.1k | return xa; |
442 | 31.1k | } |
443 | | |
444 | 486k | XmpValue::XmpStruct XmpValue::xmpStruct() const { |
445 | 486k | return xmpStruct_; |
446 | 486k | } |
447 | | |
448 | 0 | size_t XmpValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
449 | 0 | std::ostringstream os; |
450 | 0 | write(os); |
451 | 0 | std::string s = os.str(); |
452 | 0 | if (!s.empty()) |
453 | 0 | std::copy(s.begin(), s.end(), buf); |
454 | 0 | return s.size(); |
455 | 0 | } |
456 | | |
457 | 0 | int XmpValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
458 | 0 | std::string s(reinterpret_cast<const char*>(buf), len); |
459 | 0 | return read(s); |
460 | 0 | } |
461 | | |
462 | 0 | size_t XmpValue::size() const { |
463 | 0 | std::ostringstream os; |
464 | 0 | write(os); |
465 | 0 | return os.str().size(); |
466 | 0 | } |
467 | | |
468 | 206k | XmpTextValue::XmpTextValue() : XmpValue(xmpText) { |
469 | 206k | } |
470 | | |
471 | 0 | XmpTextValue::XmpTextValue(const std::string& buf) : XmpValue(xmpText) { |
472 | 0 | read(buf); |
473 | 0 | } |
474 | | |
475 | 2.38M | int XmpTextValue::read(const std::string& buf) { |
476 | | // support a type=Alt,Bag,Seq,Struct indicator |
477 | 2.38M | std::string b = buf; |
478 | 2.38M | std::string type; |
479 | 2.38M | if (buf.starts_with("type=")) { |
480 | 2.71k | std::string::size_type pos = buf.find_first_of(' '); |
481 | 2.71k | type = buf.substr(5, pos - 5); |
482 | | // Strip quotes (so you can also specify the type without quotes) |
483 | 2.71k | if (!type.empty() && type.front() == '"') |
484 | 1.03k | type = type.substr(1); |
485 | 2.71k | if (!type.empty() && type.back() == '"') |
486 | 640 | type.pop_back(); |
487 | 2.71k | b.clear(); |
488 | 2.71k | if (pos != std::string::npos) |
489 | 1.49k | b = buf.substr(pos + 1); |
490 | 2.71k | } |
491 | 2.38M | if (!type.empty()) { |
492 | 1.34k | if (type == "Alt") { |
493 | 176 | setXmpArrayType(XmpValue::xaAlt); |
494 | 1.17k | } else if (type == "Bag") { |
495 | 144 | setXmpArrayType(XmpValue::xaBag); |
496 | 1.02k | } else if (type == "Seq") { |
497 | 130 | setXmpArrayType(XmpValue::xaSeq); |
498 | 896 | } else if (type == "Struct") { |
499 | 349 | setXmpStruct(); |
500 | 547 | } else { |
501 | 547 | throw Error(ErrorCode::kerInvalidXmpText, type); |
502 | 547 | } |
503 | 1.34k | } |
504 | 2.38M | value_ = std::move(b); |
505 | 2.38M | return 0; |
506 | 2.38M | } |
507 | | |
508 | 0 | XmpTextValue::UniquePtr XmpTextValue::clone() const { |
509 | 0 | return UniquePtr(clone_()); |
510 | 0 | } |
511 | | |
512 | 109k | size_t XmpTextValue::size() const { |
513 | 109k | std::ostringstream os; |
514 | 109k | write(os); |
515 | 109k | return os.str().size(); |
516 | 109k | } |
517 | | |
518 | 109k | size_t XmpTextValue::count() const { |
519 | 109k | return size(); |
520 | 109k | } |
521 | | |
522 | 296k | std::ostream& XmpTextValue::write(std::ostream& os) const { |
523 | 296k | bool del = false; |
524 | 296k | if (xmpArrayType() != XmpValue::xaNone) { |
525 | 619 | switch (xmpArrayType()) { |
526 | 114 | case XmpValue::xaAlt: |
527 | 114 | os << "type=\"Alt\""; |
528 | 114 | break; |
529 | 8 | case XmpValue::xaBag: |
530 | 8 | os << "type=\"Bag\""; |
531 | 8 | break; |
532 | 497 | case XmpValue::xaSeq: |
533 | 497 | os << "type=\"Seq\""; |
534 | 497 | break; |
535 | 0 | case XmpValue::xaNone: |
536 | 0 | break; // just to suppress the warning |
537 | 619 | } |
538 | 619 | del = true; |
539 | 296k | } else if (xmpStruct() != XmpValue::xsNone) { |
540 | 7.59k | switch (xmpStruct()) { |
541 | 7.59k | case XmpValue::xsStruct: |
542 | 7.59k | os << "type=\"Struct\""; |
543 | 7.59k | break; |
544 | 0 | case XmpValue::xsNone: |
545 | 0 | break; // just to suppress the warning |
546 | 7.59k | } |
547 | 7.59k | del = true; |
548 | 7.59k | } |
549 | 296k | if (del && !value_.empty()) |
550 | 44 | os << " "; |
551 | 296k | return os << value_; |
552 | 296k | } |
553 | | |
554 | 0 | int64_t XmpTextValue::toInt64(size_t /*n*/) const { |
555 | 0 | return parseInt64(value_, ok_); |
556 | 0 | } |
557 | | |
558 | 0 | uint32_t XmpTextValue::toUint32(size_t /*n*/) const { |
559 | 0 | return parseUint32(value_, ok_); |
560 | 0 | } |
561 | | |
562 | 0 | float XmpTextValue::toFloat(size_t /*n*/) const { |
563 | 0 | return parseFloat(value_, ok_); |
564 | 0 | } |
565 | | |
566 | 0 | Rational XmpTextValue::toRational(size_t /*n*/) const { |
567 | 0 | return parseRational(value_, ok_); |
568 | 0 | } |
569 | | |
570 | 392k | XmpTextValue* XmpTextValue::clone_() const { |
571 | 392k | return new XmpTextValue(*this); |
572 | 392k | } |
573 | | |
574 | 22.8k | XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) { |
575 | 22.8k | setXmpArrayType(xmpArrayType(typeId)); |
576 | 22.8k | } |
577 | | |
578 | 602k | int XmpArrayValue::read(const std::string& buf) { |
579 | 602k | if (!buf.empty()) |
580 | 562k | value_.push_back(buf); |
581 | 602k | return 0; |
582 | 602k | } |
583 | | |
584 | 0 | XmpArrayValue::UniquePtr XmpArrayValue::clone() const { |
585 | 0 | return UniquePtr(clone_()); |
586 | 0 | } |
587 | | |
588 | 62.6k | size_t XmpArrayValue::count() const { |
589 | 62.6k | return value_.size(); |
590 | 62.6k | } |
591 | | |
592 | 1.54k | std::ostream& XmpArrayValue::write(std::ostream& os) const { |
593 | 1.54k | if (!value_.empty()) { |
594 | 1.22k | std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<std::string>(os, ", ")); |
595 | 1.22k | os << value_.back(); |
596 | 1.22k | } |
597 | 1.54k | return os; |
598 | 1.54k | } |
599 | | |
600 | 169k | std::string XmpArrayValue::toString(size_t n) const { |
601 | 169k | ok_ = true; |
602 | 169k | return value_.at(n); |
603 | 169k | } |
604 | | |
605 | 0 | int64_t XmpArrayValue::toInt64(size_t n) const { |
606 | 0 | return parseInt64(value_.at(n), ok_); |
607 | 0 | } |
608 | | |
609 | 0 | uint32_t XmpArrayValue::toUint32(size_t n) const { |
610 | 0 | return parseUint32(value_.at(n), ok_); |
611 | 0 | } |
612 | | |
613 | 0 | float XmpArrayValue::toFloat(size_t n) const { |
614 | 0 | return parseFloat(value_.at(n), ok_); |
615 | 0 | } |
616 | | |
617 | 0 | Rational XmpArrayValue::toRational(size_t n) const { |
618 | 0 | return parseRational(value_.at(n), ok_); |
619 | 0 | } |
620 | | |
621 | 49.2k | XmpArrayValue* XmpArrayValue::clone_() const { |
622 | 49.2k | return new XmpArrayValue(*this); |
623 | 49.2k | } |
624 | | |
625 | 1.14k | LangAltValue::LangAltValue() : XmpValue(langAlt) { |
626 | 1.14k | } |
627 | | |
628 | 0 | LangAltValue::LangAltValue(const std::string& buf) : XmpValue(langAlt) { |
629 | 0 | read(buf); |
630 | 0 | } |
631 | | |
632 | 0 | int LangAltValue::read(const std::string& buf) { |
633 | 0 | std::string b = buf; |
634 | 0 | std::string lang = "x-default"; |
635 | 0 | if (buf.starts_with("lang=")) { |
636 | 0 | static constexpr auto ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
637 | |
|
638 | 0 | const std::string::size_type pos = buf.find_first_of(' '); |
639 | 0 | if (pos == std::string::npos) { |
640 | 0 | lang = buf.substr(5); |
641 | 0 | } else { |
642 | 0 | lang = buf.substr(5, pos - 5); |
643 | 0 | } |
644 | 0 | if (lang.empty()) |
645 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
646 | | // Strip quotes (so you can also specify the language without quotes) |
647 | 0 | if (lang.front() == '"') { |
648 | 0 | lang = lang.substr(1); |
649 | |
|
650 | 0 | if (lang.empty() || lang.back() != '"') |
651 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
652 | | |
653 | 0 | lang.pop_back(); |
654 | 0 | } |
655 | | |
656 | 0 | if (lang.empty()) |
657 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
658 | | |
659 | | // Check language is in the correct format (see https://www.ietf.org/rfc/rfc3066.txt) |
660 | 0 | if (auto charPos = lang.find_first_not_of(ALPHA); charPos != std::string::npos) { |
661 | 0 | static constexpr auto ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
662 | 0 | if (lang.at(charPos) != '-' || lang.find_first_not_of(ALPHA_NUM, charPos + 1) != std::string::npos) |
663 | 0 | throw Error(ErrorCode::kerInvalidLangAltValue, buf); |
664 | 0 | } |
665 | | |
666 | 0 | b.clear(); |
667 | 0 | if (pos != std::string::npos) |
668 | 0 | b = buf.substr(pos + 1); |
669 | 0 | } |
670 | | |
671 | 0 | value_[lang] = std::move(b); |
672 | 0 | return 0; |
673 | 0 | } |
674 | | |
675 | 0 | LangAltValue::UniquePtr LangAltValue::clone() const { |
676 | 0 | return UniquePtr(clone_()); |
677 | 0 | } |
678 | | |
679 | 571 | size_t LangAltValue::count() const { |
680 | 571 | return value_.size(); |
681 | 571 | } |
682 | | |
683 | 321 | std::ostream& LangAltValue::write(std::ostream& os) const { |
684 | 321 | bool first = true; |
685 | | |
686 | | // Write the default entry first |
687 | 321 | if (auto i = value_.find("x-default"); i != value_.end()) { |
688 | 60 | os << "lang=\"" << i->first << "\" " << i->second; |
689 | 60 | first = false; |
690 | 60 | } |
691 | | |
692 | | // Write the others |
693 | 321 | for (const auto& [lang, s] : value_) { |
694 | 317 | if (lang != "x-default") { |
695 | 257 | if (!first) |
696 | 4 | os << ", "; |
697 | 257 | os << "lang=\"" << lang << "\" " << s; |
698 | 257 | first = false; |
699 | 257 | } |
700 | 317 | } |
701 | 321 | return os; |
702 | 321 | } |
703 | | |
704 | 440 | std::string LangAltValue::toString(size_t /*n*/) const { |
705 | 440 | return toString("x-default"); |
706 | 440 | } |
707 | | |
708 | 440 | std::string LangAltValue::toString(const std::string& qualifier) const { |
709 | 440 | if (auto i = value_.find(qualifier); i != value_.end()) { |
710 | 47 | ok_ = true; |
711 | 47 | return i->second; |
712 | 47 | } |
713 | 393 | ok_ = false; |
714 | 393 | return ""; |
715 | 440 | } |
716 | | |
717 | 0 | int64_t LangAltValue::toInt64(size_t /*n*/) const { |
718 | 0 | ok_ = false; |
719 | 0 | return 0; |
720 | 0 | } |
721 | | |
722 | 0 | uint32_t LangAltValue::toUint32(size_t /*n*/) const { |
723 | 0 | ok_ = false; |
724 | 0 | return 0; |
725 | 0 | } |
726 | | |
727 | 0 | float LangAltValue::toFloat(size_t /*n*/) const { |
728 | 0 | ok_ = false; |
729 | 0 | return 0.0F; |
730 | 0 | } |
731 | | |
732 | 0 | Rational LangAltValue::toRational(size_t /*n*/) const { |
733 | 0 | ok_ = false; |
734 | 0 | return {0, 0}; |
735 | 0 | } |
736 | | |
737 | 2.33k | LangAltValue* LangAltValue::clone_() const { |
738 | 2.33k | return new LangAltValue(*this); |
739 | 2.33k | } |
740 | | |
741 | 8.44k | DateValue::DateValue() : Value(date) { |
742 | 8.44k | date_ = {}; |
743 | 8.44k | } |
744 | | |
745 | 0 | DateValue::DateValue(int32_t year, int32_t month, int32_t day) : Value(date) { |
746 | 0 | date_ = {year, month, day}; |
747 | 0 | } |
748 | | |
749 | 7.99k | int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
750 | 7.99k | const std::string str(reinterpret_cast<const char*>(buf), len); |
751 | 7.99k | return read(str); |
752 | 7.99k | } |
753 | | |
754 | 8.44k | int DateValue::read(const std::string& buf) { |
755 | | // ISO 8601 date formats: |
756 | | // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf |
757 | 8.44k | size_t monthPos = 0; |
758 | 8.44k | size_t dayPos = 0; |
759 | | |
760 | 11.4k | auto printWarning = [] { |
761 | 11.4k | #ifndef SUPPRESS_WARNINGS |
762 | 11.4k | EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n"; |
763 | 11.4k | #endif |
764 | 11.4k | }; |
765 | | |
766 | 8.44k | if (buf.size() < 8) { |
767 | 1.12k | printWarning(); |
768 | 1.12k | return 1; |
769 | 1.12k | } |
770 | | |
771 | 7.31k | if ((buf.size() >= 10 && buf[4] == '-' && buf[7] == '-') || (buf.size() == 8)) { |
772 | 4.64k | if (buf.size() >= 10) { |
773 | 899 | monthPos = 5; |
774 | 899 | dayPos = 8; |
775 | 3.74k | } else { |
776 | 3.74k | monthPos = 4; |
777 | 3.74k | dayPos = 6; |
778 | 3.74k | } |
779 | | |
780 | 9.13k | auto checkDigits = [&buf, &printWarning](size_t start, size_t count, int32_t& dest) { |
781 | 27.7k | for (size_t i = start; i < start + count; ++i) { |
782 | 22.0k | if (!std::isdigit(buf[i])) { |
783 | 3.43k | printWarning(); |
784 | 3.43k | return 1; |
785 | 3.43k | } |
786 | 22.0k | } |
787 | 5.69k | dest = std::stoul(buf.substr(start, count)); |
788 | 5.69k | return 0; |
789 | 9.13k | }; |
790 | | |
791 | 4.64k | if (checkDigits(0, 4, date_.year) || checkDigits(monthPos, 2, date_.month) || checkDigits(dayPos, 2, date_.day)) { |
792 | 3.43k | printWarning(); |
793 | 3.43k | return 1; |
794 | 3.43k | } |
795 | | |
796 | 1.20k | if (date_.month > 12 || date_.day > 31) { |
797 | 758 | date_.month = 0; |
798 | 758 | date_.day = 0; |
799 | 758 | printWarning(); |
800 | 758 | return 1; |
801 | 758 | } |
802 | 448 | return 0; |
803 | 1.20k | } |
804 | 2.67k | printWarning(); |
805 | 2.67k | return 1; |
806 | 7.31k | } |
807 | | |
808 | 0 | void DateValue::setDate(const Date& src) { |
809 | 0 | date_ = src; |
810 | 0 | } |
811 | | |
812 | 189 | size_t DateValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
813 | | // \note Here the date is copied in the Basic format YYYYMMDD, as the IPTC key Iptc.Application2.DateCreated |
814 | | // wants it. Check https://exiv2.org/iptc.html |
815 | | |
816 | 189 | auto out = reinterpret_cast<char*>(buf); |
817 | 189 | auto it = stringFormatTo(out, "{:04}{:02}{:02}", date_.year, date_.month, date_.day); |
818 | | |
819 | 189 | return it - out; |
820 | 189 | } |
821 | | |
822 | 0 | const DateValue::Date& DateValue::getDate() const { |
823 | 0 | return date_; |
824 | 0 | } |
825 | | |
826 | 0 | size_t DateValue::count() const { |
827 | 0 | return size(); |
828 | 0 | } |
829 | | |
830 | 378 | size_t DateValue::size() const { |
831 | 378 | return 8; |
832 | 378 | } |
833 | | |
834 | 2.19k | DateValue* DateValue::clone_() const { |
835 | 2.19k | return new DateValue(*this); |
836 | 2.19k | } |
837 | | |
838 | 382 | std::ostream& DateValue::write(std::ostream& os) const { |
839 | | // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD |
840 | 382 | return os << stringFormat("{:04}-{:02}-{:02}", date_.year, date_.month, date_.day); |
841 | 382 | } |
842 | | |
843 | 0 | int64_t DateValue::toInt64(size_t /*n*/) const { |
844 | | // Range of tm struct is limited to about 1970 to 2038 |
845 | | // This will return -1 if outside that range |
846 | 0 | std::tm tms = {}; |
847 | 0 | tms.tm_mday = date_.day; |
848 | 0 | tms.tm_mon = date_.month - 1; |
849 | 0 | tms.tm_year = date_.year - 1900; |
850 | 0 | auto l = static_cast<int64_t>(std::mktime(&tms)); |
851 | 0 | ok_ = (l != -1); |
852 | 0 | return l; |
853 | 0 | } |
854 | | |
855 | 0 | uint32_t DateValue::toUint32(size_t /*n*/) const { |
856 | 0 | const int64_t t = toInt64(); |
857 | 0 | if (t < 0 || t > std::numeric_limits<uint32_t>::max()) { |
858 | 0 | ok_ = false; |
859 | 0 | return 0; |
860 | 0 | } |
861 | 0 | return static_cast<uint32_t>(t); |
862 | 0 | } |
863 | | |
864 | 0 | float DateValue::toFloat(size_t n) const { |
865 | 0 | return static_cast<float>(toInt64(n)); |
866 | 0 | } |
867 | | |
868 | 0 | Rational DateValue::toRational(size_t n) const { |
869 | 0 | const int64_t t = toInt64(n); |
870 | 0 | if (t < std::numeric_limits<int32_t>::min() || t > std::numeric_limits<int32_t>::max()) { |
871 | 0 | ok_ = false; |
872 | 0 | return {0, 1}; |
873 | 0 | } |
874 | 0 | return {static_cast<int32_t>(t), 1}; |
875 | 0 | } |
876 | | |
877 | 15.4k | TimeValue::TimeValue() : Value(time) { |
878 | 15.4k | time_ = {}; |
879 | 15.4k | } |
880 | | |
881 | 0 | TimeValue::TimeValue(int32_t hour, int32_t minute, int32_t second, int32_t tzHour, int32_t tzMinute) : Value(date) { |
882 | 0 | time_ = {hour, minute, second, tzHour, tzMinute}; |
883 | 0 | } |
884 | | |
885 | 15.4k | int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) { |
886 | 15.4k | const std::string str(reinterpret_cast<const char*>(buf), len); |
887 | 15.4k | return read(str); |
888 | 15.4k | } |
889 | | |
890 | 15.4k | int TimeValue::read(const std::string& buf) { |
891 | | // ISO 8601 time formats: |
892 | | // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf |
893 | | // Not supported formats: |
894 | | // 4.2.2.4 Representations with decimal fraction: 232050,5 |
895 | 15.4k | auto printWarning = [] { |
896 | 11.3k | #ifndef SUPPRESS_WARNINGS |
897 | 11.3k | EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n"; |
898 | 11.3k | #endif |
899 | 11.3k | return 1; |
900 | 11.3k | }; |
901 | | |
902 | 15.4k | if (buf.size() < 2) |
903 | 1.37k | return printWarning(); |
904 | | |
905 | 14.0k | for (auto c : buf) |
906 | 200k | if (c != ':' && c != '+' && c != '-' && c != 'Z' && !std::isdigit(c)) |
907 | 5.05k | return printWarning(); |
908 | | |
909 | 9.02k | size_t mpos; |
910 | 9.02k | size_t spos; |
911 | 9.02k | if (buf.find(':') != std::string::npos) { |
912 | 3.02k | mpos = 3; |
913 | 3.02k | spos = 6; |
914 | 6.00k | } else { |
915 | 6.00k | mpos = 2; |
916 | 6.00k | spos = 4; |
917 | 6.00k | } |
918 | | |
919 | 9.02k | auto hi = std::stoi(buf.substr(0, 2)); |
920 | 9.02k | if (hi < 0 || hi > 23) |
921 | 298 | return printWarning(); |
922 | 8.72k | time_.hour = hi; |
923 | 8.72k | if (buf.size() > 3) { |
924 | 7.36k | auto mi = std::stoi(buf.substr(mpos, 2)); |
925 | 7.36k | if (mi < 0 || mi > 59) |
926 | 507 | return printWarning(); |
927 | 6.86k | time_.minute = std::stoi(buf.substr(mpos, 2)); |
928 | 6.86k | } else { |
929 | 1.35k | time_.minute = 0; |
930 | 1.35k | } |
931 | 8.22k | if (buf.size() > 5) { |
932 | 5.96k | auto si = std::stoi(buf.substr(spos, 2)); |
933 | 5.96k | if (si < 0 || si > 60) |
934 | 1.23k | return printWarning(); |
935 | 4.72k | time_.second = std::stoi(buf.substr(spos, 2)); |
936 | 4.72k | } else { |
937 | 2.26k | time_.second = 0; |
938 | 2.26k | } |
939 | | |
940 | 6.98k | auto fpos = buf.find('+'); |
941 | 6.98k | if (fpos == std::string::npos) |
942 | 4.29k | fpos = buf.find('-'); |
943 | | |
944 | 6.98k | if (fpos != std::string::npos) { |
945 | 5.99k | auto format = buf.substr(fpos, buf.size()); |
946 | 5.99k | auto posColon = format.find(':'); |
947 | 5.99k | if (posColon == std::string::npos) { |
948 | | // Extended format |
949 | 3.67k | auto tzhi = std::stoi(format.substr(0, 3)); |
950 | 3.67k | if (tzhi < -23 || tzhi > 23) |
951 | 729 | return printWarning(); |
952 | 2.94k | time_.tzHour = tzhi; |
953 | 2.94k | if (format.size() > 3) { |
954 | 2.08k | int minute = std::stoi(format.substr(3)); |
955 | 2.08k | if (minute < 0 || minute > 59) |
956 | 589 | return printWarning(); |
957 | 1.49k | time_.tzMinute = time_.tzHour < 0 ? -minute : minute; |
958 | 1.49k | } |
959 | 2.94k | } else { |
960 | | // Basic format |
961 | 2.32k | auto tzhi = std::stoi(format.substr(0, posColon)); |
962 | 2.32k | if (tzhi < -23 || tzhi > 23) |
963 | 713 | return printWarning(); |
964 | 1.60k | time_.tzHour = tzhi; |
965 | 1.60k | int minute = std::stoi(format.substr(posColon + 1)); |
966 | 1.60k | if (minute < 0 || minute > 59) |
967 | 816 | return printWarning(); |
968 | 793 | time_.tzMinute = time_.tzHour < 0 ? -minute : minute; |
969 | 793 | } |
970 | 5.99k | } |
971 | 4.13k | return 0; |
972 | 6.98k | } |
973 | | |
974 | | /// \todo not used internally. At least we should test it |
975 | 0 | void TimeValue::setTime(const Time& src) { |
976 | 0 | time_ = src; |
977 | 0 | } |
978 | | |
979 | 496 | size_t TimeValue::copy(byte* buf, ByteOrder /*byteOrder*/) const { |
980 | | // NOTE: Here the time is copied in the Basic format HHMMSS:HHMM, as the IPTC key |
981 | | // Iptc.Application2.TimeCreated wants it. Check https://exiv2.org/iptc.html |
982 | 496 | char plusMinus = '+'; |
983 | 496 | if (time_.tzHour < 0 || time_.tzMinute < 0) |
984 | 129 | plusMinus = '-'; |
985 | | |
986 | 496 | auto out = reinterpret_cast<char*>(buf); |
987 | 496 | auto it = stringFormatTo(out, "{:02}{:02}{:02}{}{:02}{:02}", time_.hour, time_.minute, time_.second, plusMinus, |
988 | 496 | std::abs(time_.tzHour), std::abs(time_.tzMinute)); |
989 | | |
990 | 496 | auto wrote = static_cast<size_t>(it - out); |
991 | 496 | Internal::enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat); |
992 | 496 | return wrote; |
993 | 496 | } |
994 | | |
995 | 0 | const TimeValue::Time& TimeValue::getTime() const { |
996 | 0 | return time_; |
997 | 0 | } |
998 | | |
999 | 0 | size_t TimeValue::count() const { |
1000 | 0 | return size(); |
1001 | 0 | } |
1002 | | |
1003 | 992 | size_t TimeValue::size() const { |
1004 | 992 | return 11; |
1005 | 992 | } |
1006 | | |
1007 | 6.35k | TimeValue* TimeValue::clone_() const { |
1008 | 6.35k | return new TimeValue(*this); |
1009 | 6.35k | } |
1010 | | |
1011 | 126 | std::ostream& TimeValue::write(std::ostream& os) const { |
1012 | | // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm |
1013 | 126 | char plusMinus = '+'; |
1014 | 126 | if (time_.tzHour < 0 || time_.tzMinute < 0) |
1015 | 32 | plusMinus = '-'; |
1016 | | |
1017 | 126 | return os << stringFormat("{:02}:{:02}:{:02}{}{:02}:{:02}", time_.hour, time_.minute, time_.second, plusMinus, |
1018 | 126 | std::abs(time_.tzHour), std::abs(time_.tzMinute)); |
1019 | 126 | } |
1020 | | |
1021 | 0 | int64_t TimeValue::toInt64(size_t /*n*/) const { |
1022 | | // Returns number of seconds in the day in UTC. |
1023 | 0 | auto result = static_cast<int64_t>(time_.hour - time_.tzHour) * 60 * 60; |
1024 | 0 | result += static_cast<int64_t>(time_.minute - time_.tzMinute) * 60; |
1025 | 0 | result += time_.second; |
1026 | 0 | if (result < 0) { |
1027 | 0 | result += 86400; |
1028 | 0 | } |
1029 | 0 | ok_ = true; |
1030 | 0 | return result; |
1031 | 0 | } |
1032 | | |
1033 | 0 | uint32_t TimeValue::toUint32(size_t /*n*/) const { |
1034 | 0 | return static_cast<uint32_t>(std::clamp<int64_t>(toInt64(), 0, std::numeric_limits<uint32_t>::max())); |
1035 | 0 | } |
1036 | | |
1037 | 0 | float TimeValue::toFloat(size_t n) const { |
1038 | 0 | return static_cast<float>(toInt64(n)); |
1039 | 0 | } |
1040 | | |
1041 | 0 | Rational TimeValue::toRational(size_t n) const { |
1042 | 0 | return {static_cast<int32_t>(toInt64(n)), 1}; |
1043 | 0 | } |
1044 | | |
1045 | | } // namespace Exiv2 |