File indexing completed on 2025-01-05 03:56:30

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-09-15
0007  * Description : Exiv2 library interface.
0008  *               Xmp manipulation methods.
0009  *
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 // Local includes
0018 
0019 #include "metaengine_p.h"
0020 #include "digikam_debug.h"
0021 #include "digikam_config.h"
0022 
0023 #if defined(Q_CC_CLANG)
0024 #   pragma clang diagnostic push
0025 #   pragma clang diagnostic ignored "-Wdeprecated-declarations"
0026 #endif
0027 
0028 namespace Digikam
0029 {
0030 
0031 bool MetaEngine::canWriteXmp(const QString& filePath)
0032 {
0033 
0034 #ifdef _XMP_SUPPORT_
0035 
0036     QMutexLocker lock(&s_metaEngineMutex);
0037 
0038     try
0039     {
0040 #if defined Q_OS_WIN && defined EXV_UNICODE_PATH
0041 
0042         Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const wchar_t*)filePath.utf16());
0043 
0044 #elif defined Q_OS_WIN
0045 
0046         Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(QFile::encodeName(filePath).constData());
0047 
0048 #else
0049 
0050         Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(filePath.toUtf8().constData());
0051 
0052 #endif
0053 
0054         Exiv2::AccessMode mode = image->checkMode(Exiv2::mdXmp);
0055 
0056         return ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite));
0057     }
0058     catch (Exiv2::AnyError& e)
0059     {
0060         qCCritical(DIGIKAM_METAENGINE_LOG) << "Cannot check Xmp access mode with Exiv2:(Error #"
0061                                            << (int)e.code() << ": " << QString::fromStdString(e.what()) << ")";
0062     }
0063     catch (...)
0064     {
0065         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0066     }
0067 
0068 #else
0069 
0070     Q_UNUSED(filePath);
0071 
0072 #endif // _XMP_SUPPORT_
0073 
0074     return false;
0075 }
0076 
0077 bool MetaEngine::hasXmp() const
0078 {
0079 
0080 #ifdef _XMP_SUPPORT_
0081 
0082     return !d->xmpMetadata().empty();
0083 
0084 #else
0085 
0086     return false;
0087 
0088 #endif // _XMP_SUPPORT_
0089 
0090 }
0091 
0092 bool MetaEngine::clearXmp() const
0093 {
0094 
0095 #ifdef _XMP_SUPPORT_
0096 
0097     QMutexLocker lock(&s_metaEngineMutex);
0098 
0099     try
0100     {
0101         d->xmpMetadata().clear();
0102 
0103         return true;
0104     }
0105     catch (Exiv2::AnyError& e)
0106     {
0107         d->printExiv2ExceptionError(QLatin1String("Cannot clear Xmp data with Exiv2:"), e);
0108     }
0109     catch (...)
0110     {
0111         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0112     }
0113 
0114 #endif // _XMP_SUPPORT_
0115 
0116     return false;
0117 }
0118 
0119 QByteArray MetaEngine::getXmp() const
0120 {
0121 
0122 #ifdef _XMP_SUPPORT_
0123 
0124     QMutexLocker lock(&s_metaEngineMutex);
0125 
0126     try
0127     {
0128         if (!d->xmpMetadata().empty())
0129         {
0130 
0131             std::string xmpPacket;
0132             Exiv2::XmpParser::encode(xmpPacket, d->xmpMetadata());
0133             QByteArray data(xmpPacket.data(), (int)xmpPacket.size());
0134 
0135             return data;
0136         }
0137     }
0138     catch (Exiv2::AnyError& e)
0139     {
0140         if (!d->filePath.isEmpty())
0141         {
0142             d->printExiv2ExceptionError(QLatin1String("Cannot get Xmp data with Exiv2:"), e);
0143         }
0144     }
0145     catch (...)
0146     {
0147         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0148     }
0149 
0150 #endif // _XMP_SUPPORT_
0151 
0152     return QByteArray();
0153 }
0154 
0155 bool MetaEngine::setXmp(const QByteArray& data) const
0156 {
0157 
0158 #ifdef _XMP_SUPPORT_
0159 
0160     QMutexLocker lock(&s_metaEngineMutex);
0161 
0162     try
0163     {
0164         if (!data.isEmpty())
0165         {
0166             std::string xmpPacket;
0167             xmpPacket.assign(data.data(), data.size());
0168 
0169             if (Exiv2::XmpParser::decode(d->xmpMetadata(), xmpPacket) != 0)
0170             {
0171                 return false;
0172             }
0173             else
0174             {
0175                 return true;
0176             }
0177         }
0178     }
0179     catch (Exiv2::AnyError& e)
0180     {
0181         if (!d->filePath.isEmpty())
0182         {
0183             qCCritical(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData();
0184         }
0185 
0186         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp data with Exiv2:"), e);
0187     }
0188     catch (...)
0189     {
0190         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0191     }
0192 
0193 #else
0194 
0195     Q_UNUSED(data);
0196 
0197 #endif // _XMP_SUPPORT_
0198 
0199     return false;
0200 }
0201 
0202 MetaEngine::MetaDataMap MetaEngine::getXmpTagsDataList(const QStringList& xmpKeysFilter, bool invertSelection) const
0203 {
0204 
0205 #ifdef _XMP_SUPPORT_
0206 
0207     if (d->xmpMetadata().empty())
0208     {
0209        return MetaDataMap();
0210     }
0211 
0212     QMutexLocker lock(&s_metaEngineMutex);
0213 
0214     try
0215     {
0216         Exiv2::XmpData xmpData = d->xmpMetadata();
0217         xmpData.sortByKey();
0218 
0219         QString     ifDItemName;
0220         MetaDataMap metaDataMap;
0221 
0222         for (Exiv2::XmpData::const_iterator md = xmpData.begin() ; md != xmpData.end() ; ++md)
0223         {
0224             QString key = QLatin1String(md->key().c_str());
0225 
0226             // Decode the tag value with a user friendly output.
0227 
0228             std::ostringstream os;
0229             os << *md;
0230             QString value = QString::fromStdString(os.str());
0231 
0232             // If the tag is a language alternative type, parse content to detect language.
0233 
0234             if (md->typeId() == Exiv2::langAlt)
0235             {
0236                 QString lang;
0237                 value = detectLanguageAlt(value, lang);
0238             }
0239             else
0240             {
0241                 value = QString::fromStdString(os.str());
0242             }
0243 
0244             // To make a string just on one line.
0245 
0246             value.replace(QLatin1Char('\n'), QLatin1String(" "));
0247 
0248             // Some XMP key are redondancy. check if already one exist...
0249 
0250             MetaDataMap::const_iterator it = metaDataMap.constFind(key);
0251 
0252             // We apply a filter to get only the XMP tags that we need.
0253 
0254             if (!xmpKeysFilter.isEmpty())
0255             {
0256                 if (!invertSelection)
0257                 {
0258                     if (xmpKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1)))
0259                     {
0260                         if (it == metaDataMap.constEnd())
0261                         {
0262                             metaDataMap.insert(key, value);
0263                         }
0264                         else
0265                         {
0266                             QString v = *it;
0267                             v.append(QLatin1String(", "));
0268                             v.append(value);
0269                             metaDataMap.insert(key, v);
0270                         }
0271                     }
0272                 }
0273                 else
0274                 {
0275                     if (!xmpKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1)))
0276                     {
0277                         if (it == metaDataMap.constEnd())
0278                         {
0279                             metaDataMap.insert(key, value);
0280                         }
0281                         else
0282                         {
0283                             QString v = *it;
0284                             v.append(QLatin1String(", "));
0285                             v.append(value);
0286                             metaDataMap.insert(key, v);
0287                         }
0288                     }
0289                 }
0290             }
0291             else // else no filter at all.
0292             {
0293                 if (it == metaDataMap.constEnd())
0294                 {
0295                     metaDataMap.insert(key, value);
0296                 }
0297                 else
0298                 {
0299                     QString v = *it;
0300                     v.append(QLatin1String(", "));
0301                     v.append(value);
0302                     metaDataMap.insert(key, v);
0303                 }
0304             }
0305         }
0306 
0307         return metaDataMap;
0308     }
0309     catch (Exiv2::AnyError& e)
0310     {
0311         d->printExiv2ExceptionError(QLatin1String("Cannot parse Xmp metadata with Exiv2:"), e);
0312     }
0313     catch (...)
0314     {
0315         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0316     }
0317 
0318 #else
0319 
0320     Q_UNUSED(xmpKeysFilter);
0321     Q_UNUSED(invertSelection);
0322 
0323 #endif // _XMP_SUPPORT_
0324 
0325     return MetaDataMap();
0326 }
0327 
0328 QString MetaEngine::getXmpTagTitle(const char* xmpTagName)
0329 {
0330 
0331 #ifdef _XMP_SUPPORT_
0332 
0333     QMutexLocker lock(&s_metaEngineMutex);
0334 
0335     try
0336     {
0337         std::string xmpkey(xmpTagName);
0338         Exiv2::XmpKey xk(xmpkey);
0339 
0340         return QString::fromLocal8Bit(Exiv2::XmpProperties::propertyTitle(xk));
0341     }
0342     catch (Exiv2::AnyError& e)
0343     {
0344         d->printExiv2ExceptionError(QLatin1String("Cannot get Xmp metadata tag title with Exiv2:"), e);
0345     }
0346     catch (...)
0347     {
0348         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0349     }
0350 
0351 #else
0352 
0353     Q_UNUSED(xmpTagName);
0354 
0355 #endif // _XMP_SUPPORT_
0356 
0357     return QString();
0358 }
0359 
0360 QString MetaEngine::getXmpTagDescription(const char* xmpTagName)
0361 {
0362 
0363 #ifdef _XMP_SUPPORT_
0364 
0365     try
0366     {
0367         std::string xmpkey(xmpTagName);
0368         Exiv2::XmpKey xk(xmpkey);
0369 
0370         return QString::fromLocal8Bit(Exiv2::XmpProperties::propertyDesc(xk));
0371     }
0372     catch (Exiv2::AnyError& e)
0373     {
0374         d->printExiv2ExceptionError(QLatin1String("Cannot get Xmp metadata tag description with Exiv2:"), e);
0375     }
0376     catch (...)
0377     {
0378         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0379     }
0380 
0381 #else
0382 
0383     Q_UNUSED(xmpTagName);
0384 
0385 #endif // _XMP_SUPPORT_
0386 
0387     return QString();
0388 }
0389 
0390 QString MetaEngine::getXmpTagString(const char* xmpTagName, bool escapeCR) const
0391 {
0392 
0393 #ifdef _XMP_SUPPORT_
0394 
0395     try
0396     {
0397         Exiv2::XmpData xmpData(d->xmpMetadata());
0398         Exiv2::XmpKey key(xmpTagName);
0399         Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
0400 
0401         if (it != xmpData.end())
0402         {
0403             std::ostringstream os;
0404             os << *it;
0405             QString tagValue = QString::fromStdString(os.str());
0406 
0407             if (escapeCR)
0408             {
0409                 tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
0410             }
0411 
0412             return tagValue;
0413         }
0414     }
0415     catch (Exiv2::AnyError& e)
0416     {
0417         d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image with Exiv2:")
0418                                     .arg(QLatin1String(xmpTagName)), e);
0419     }
0420     catch (...)
0421     {
0422         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0423     }
0424 
0425 #else
0426 
0427     Q_UNUSED(xmpTagName);
0428     Q_UNUSED(escapeCR);
0429 
0430 #endif // _XMP_SUPPORT_
0431 
0432     return QString();
0433 }
0434 
0435 bool MetaEngine::setXmpTagString(const char* xmpTagName, const QString& value) const
0436 {
0437 
0438 #ifdef _XMP_SUPPORT_
0439 
0440     try
0441     {
0442         Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::xmpText);
0443         xmpTxtVal->read(value.toStdString());
0444         d->xmpMetadata()[xmpTagName].setValue(xmpTxtVal.get());
0445 
0446         return true;
0447     }
0448     catch (Exiv2::AnyError& e)
0449     {
0450         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string into image with Exiv2:"), e);
0451     }
0452     catch (...)
0453     {
0454         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0455     }
0456 
0457 #else
0458 
0459     Q_UNUSED(xmpTagName);
0460     Q_UNUSED(value);
0461 
0462 #endif // _XMP_SUPPORT_
0463 
0464     return false;
0465 }
0466 bool MetaEngine::setXmpTagString(const char* xmpTagName,
0467                                  const QString& value,
0468                                  MetaEngine::XmpTagType type) const
0469 {
0470 #ifdef _XMP_SUPPORT_
0471 
0472     try
0473     {
0474         Exiv2::XmpTextValue xmpTxtVal("");
0475 
0476         if (type == MetaEngine::NormalTag)      // normal type
0477         {
0478             xmpTxtVal.read(value.toStdString());
0479             d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), &xmpTxtVal);
0480 
0481             return true;
0482         }
0483 
0484         if (type == MetaEngine::ArrayBagTag)    // xmp type = bag
0485         {
0486             xmpTxtVal.setXmpArrayType(Exiv2::XmpValue::xaBag);
0487             xmpTxtVal.read("");
0488             d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), &xmpTxtVal);
0489 
0490             return true;
0491         }
0492 
0493         if (type == MetaEngine::StructureTag)   // xmp type = struct
0494         {
0495             xmpTxtVal.setXmpStruct();
0496             d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), &xmpTxtVal);
0497 
0498             return true;
0499         }
0500     }
0501     catch (Exiv2::AnyError& e)
0502     {
0503         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string into image with Exiv2:"), e);
0504     }
0505     catch (...)
0506     {
0507         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0508     }
0509 
0510 #else
0511 
0512     Q_UNUSED(xmpTagName);
0513     Q_UNUSED(value);
0514 
0515 #endif // _XMP_SUPPORT_
0516 
0517     return false;
0518 }
0519 
0520 MetaEngine::AltLangMap MetaEngine::getXmpTagStringListLangAlt(const char* xmpTagName, bool escapeCR) const
0521 {
0522 
0523 #ifdef _XMP_SUPPORT_
0524 
0525     try
0526     {
0527         Exiv2::XmpData xmpData = d->xmpMetadata();
0528 
0529         for (Exiv2::XmpData::const_iterator it = xmpData.begin() ; it != xmpData.end() ; ++it)
0530         {
0531             if ((it->key() == xmpTagName) && (it->typeId() == Exiv2::langAlt))
0532             {
0533                 AltLangMap map;
0534                 const Exiv2::LangAltValue &value = static_cast<const Exiv2::LangAltValue &>(it->value());
0535 
0536                 for (Exiv2::LangAltValue::ValueType::const_iterator it2 = value.value_.begin() ;
0537                      it2 != value.value_.end() ; ++it2)
0538                 {
0539                     QString lang = QString::fromUtf8(it2->first.c_str());
0540                     QString text = QString::fromUtf8(it2->second.c_str());
0541 
0542                     if (escapeCR)
0543                     {
0544                         text.replace(QLatin1Char('\n'), QLatin1String(" "));
0545                     }
0546 
0547                     map.insert(lang, text);
0548                 }
0549 
0550                 return map;
0551             }
0552         }
0553     }
0554     catch (Exiv2::AnyError& e)
0555     {
0556         d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image with Exiv2:")
0557                                     .arg(QLatin1String(xmpTagName)), e);
0558     }
0559     catch (...)
0560     {
0561         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0562     }
0563 
0564 #else
0565 
0566     Q_UNUSED(xmpTagName);
0567     Q_UNUSED(escapeCR);
0568 
0569 #endif // _XMP_SUPPORT_
0570 
0571     return AltLangMap();
0572 }
0573 
0574 bool MetaEngine::setXmpTagStringListLangAlt(const char* xmpTagName, const MetaEngine::AltLangMap& values) const
0575 {
0576 
0577 #ifdef _XMP_SUPPORT_
0578 
0579     try
0580     {
0581         // Remove old XMP alternative Language tag.
0582 
0583         removeXmpTag(xmpTagName);
0584 
0585         if (!values.isEmpty())
0586         {
0587             Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::langAlt);
0588 
0589             for (AltLangMap::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
0590             {
0591                 QString lang       = it.key();
0592                 QString text       = it.value();
0593                 QString txtLangAlt = QString::fromLatin1("lang=%1 %2").arg(lang).arg(text);
0594                 xmpTxtVal->read(txtLangAlt.toStdString());
0595             }
0596 
0597             // ...and add the new one instead.
0598 
0599             d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), xmpTxtVal.get());
0600         }
0601 
0602         return true;
0603     }
0604     catch (Exiv2::AnyError& e)
0605     {
0606         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string lang-alt into image with Exiv2:"), e);
0607     }
0608     catch (...)
0609     {
0610         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0611     }
0612 
0613 #else
0614 
0615     Q_UNUSED(xmpTagName);
0616     Q_UNUSED(values);
0617 
0618 #endif // _XMP_SUPPORT_
0619 
0620     return false;
0621 }
0622 
0623 QString MetaEngine::getXmpTagStringLangAlt(const char* xmpTagName, const QString& langAlt, bool escapeCR) const
0624 {
0625 
0626 #ifdef _XMP_SUPPORT_
0627 
0628     try
0629     {
0630         Exiv2::XmpData xmpData(d->xmpMetadata());
0631         Exiv2::XmpKey key(xmpTagName);
0632 
0633         for (Exiv2::XmpData::const_iterator it = xmpData.begin() ; it != xmpData.end() ; ++it)
0634         {
0635             if ((it->key() == xmpTagName) && (it->typeId() == Exiv2::langAlt))
0636             {
0637                 for (int i = 0 ; i < (int)it->count() ; ++i)
0638                 {
0639                     std::ostringstream os;
0640                     os << it->toString(i);
0641                     QString lang;
0642                     QString tagValue = QString::fromStdString(os.str());
0643                     tagValue         = detectLanguageAlt(tagValue, lang);
0644 
0645                     if (langAlt == lang)
0646                     {
0647                         if (escapeCR)
0648                         {
0649                             tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
0650                         }
0651 
0652                         return tagValue;
0653                     }
0654                 }
0655             }
0656         }
0657     }
0658     catch (Exiv2::AnyError& e)
0659     {
0660         d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image with Exiv2:")
0661                                     .arg(QLatin1String(xmpTagName)), e);
0662     }
0663     catch (...)
0664     {
0665         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0666     }
0667 
0668 #else
0669 
0670     Q_UNUSED(xmpTagName);
0671     Q_UNUSED(langAlt);
0672     Q_UNUSED(escapeCR);
0673 
0674 #endif // _XMP_SUPPORT_
0675 
0676     return QString();
0677 }
0678 
0679 bool MetaEngine::setXmpTagStringLangAlt(const char* xmpTagName,
0680                                         const QString& value,
0681                                         const QString& langAlt) const
0682 {
0683 
0684 #ifdef _XMP_SUPPORT_
0685 
0686     try
0687     {
0688         QString language(QLatin1String("x-default")); // default alternative language.
0689 
0690         if (!langAlt.isEmpty())
0691         {
0692             language = langAlt;
0693         }
0694 
0695         QString txtLangAlt              = QString::fromLatin1("lang=%1 %2").arg(language).arg(value);
0696         Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::langAlt);
0697 
0698         // Search if an Xmp tag already exist.
0699 
0700         AltLangMap map = getXmpTagStringListLangAlt(xmpTagName, false);
0701 
0702         if (!map.isEmpty())
0703         {
0704             for (AltLangMap::const_iterator it = map.constBegin() ; it != map.constEnd() ; ++it)
0705             {
0706                 if (it.key() != langAlt)
0707                 {
0708                     xmpTxtVal->read((*it).toStdString());
0709                     qCDebug(DIGIKAM_METAENGINE_LOG) << *it;
0710                 }
0711             }
0712         }
0713 
0714         xmpTxtVal->read(txtLangAlt.toStdString());
0715         removeXmpTag(xmpTagName);
0716         d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), xmpTxtVal.get());
0717 
0718         return true;
0719     }
0720     catch (Exiv2::AnyError& e)
0721     {
0722         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string lang-alt into image with Exiv2:"), e);
0723     }
0724     catch (...)
0725     {
0726         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0727     }
0728 
0729 #else
0730 
0731     Q_UNUSED(xmpTagName);
0732     Q_UNUSED(value);
0733     Q_UNUSED(langAlt);
0734 
0735 #endif // _XMP_SUPPORT_
0736 
0737     return false;
0738 }
0739 
0740 QStringList MetaEngine::getXmpTagStringSeq(const char* xmpTagName, bool escapeCR) const
0741 {
0742 
0743 #ifdef _XMP_SUPPORT_
0744 
0745     try
0746     {
0747         Exiv2::XmpData xmpData(d->xmpMetadata());
0748         Exiv2::XmpKey key(xmpTagName);
0749         Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
0750 
0751         if (it != xmpData.end())
0752         {
0753             if (it->typeId() == Exiv2::xmpSeq)
0754             {
0755                 QStringList seq;
0756 
0757                 for (int i = 0 ; i < (int)it->count() ; ++i)
0758                 {
0759                     std::ostringstream os;
0760                     os << it->toString(i);
0761                     QString seqValue = QString::fromStdString(os.str());
0762 
0763                     if (escapeCR)
0764                     {
0765                         seqValue.replace(QLatin1Char('\n'), QLatin1String(" "));
0766                     }
0767 
0768                     seq.append(seqValue);
0769                 }
0770 
0771                 qCDebug(DIGIKAM_METAENGINE_LOG) << "XMP String Seq (" << xmpTagName << "): " << seq;
0772 
0773                 return seq;
0774             }
0775         }
0776     }
0777     catch (Exiv2::AnyError& e)
0778     {
0779         d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image with Exiv2:")
0780                                     .arg(QLatin1String(xmpTagName)), e);
0781     }
0782     catch (...)
0783     {
0784         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0785     }
0786 
0787 #else
0788 
0789     Q_UNUSED(xmpTagName);
0790     Q_UNUSED(escapeCR);
0791 
0792 #endif // _XMP_SUPPORT_
0793 
0794     return QStringList();
0795 }
0796 
0797 bool MetaEngine::setXmpTagStringSeq(const char* xmpTagName, const QStringList& seq) const
0798 {
0799 #ifdef _XMP_SUPPORT_
0800 
0801     try
0802     {
0803         if (seq.isEmpty())
0804         {
0805             removeXmpTag(xmpTagName);
0806         }
0807         else
0808         {
0809             const QStringList list          = seq;
0810             Exiv2::Value::AutoPtr xmpTxtSeq = Exiv2::Value::create(Exiv2::xmpSeq);
0811 
0812             for (QStringList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it)
0813             {
0814                 xmpTxtSeq->read((*it).toStdString());
0815             }
0816 
0817             d->xmpMetadata()[xmpTagName].setValue(xmpTxtSeq.get());
0818         }
0819 
0820         return true;
0821     }
0822     catch (Exiv2::AnyError& e)
0823     {
0824         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string Seq into image with Exiv2:"), e);
0825     }
0826     catch (...)
0827     {
0828         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0829     }
0830 
0831 #else
0832 
0833     Q_UNUSED(xmpTagName);
0834     Q_UNUSED(seq);
0835 
0836 #endif // _XMP_SUPPORT_
0837 
0838     return false;
0839 }
0840 
0841 QStringList MetaEngine::getXmpTagStringBag(const char* xmpTagName, bool escapeCR) const
0842 {
0843 
0844 #ifdef _XMP_SUPPORT_
0845 
0846     try
0847     {
0848         Exiv2::XmpData xmpData(d->xmpMetadata());
0849         Exiv2::XmpKey key(xmpTagName);
0850         Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
0851 
0852         if (it != xmpData.end())
0853         {
0854             if (it->typeId() == Exiv2::xmpBag)
0855             {
0856                 QStringList bag;
0857 
0858                 for (int i = 0 ; i < (int)it->count() ; ++i)
0859                 {
0860                     std::ostringstream os;
0861                     os << it->toString(i);
0862                     QString bagValue = QString::fromStdString(os.str());
0863 
0864                     if (escapeCR)
0865                     {
0866                         bagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
0867                     }
0868 
0869                     bag.append(bagValue);
0870                 }
0871 
0872                 return bag;
0873             }
0874         }
0875     }
0876     catch (Exiv2::AnyError& e)
0877     {
0878         d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image with Exiv2:")
0879                                     .arg(QLatin1String(xmpTagName)), e);
0880     }
0881     catch (...)
0882     {
0883         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0884     }
0885 
0886 #else
0887 
0888     Q_UNUSED(xmpTagName);
0889     Q_UNUSED(escapeCR);
0890 
0891 #endif // _XMP_SUPPORT_
0892 
0893     return QStringList();
0894 }
0895 
0896 bool MetaEngine::setXmpTagStringBag(const char* xmpTagName, const QStringList& bag) const
0897 {
0898 
0899 #ifdef _XMP_SUPPORT_
0900 
0901     try
0902     {
0903         if (bag.isEmpty())
0904         {
0905             removeXmpTag(xmpTagName);
0906         }
0907         else
0908         {
0909             QStringList list                = bag;
0910             Exiv2::Value::AutoPtr xmpTxtBag = Exiv2::Value::create(Exiv2::xmpBag);
0911 
0912             for (QStringList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it)
0913             {
0914                 xmpTxtBag->read((*it).toStdString());
0915             }
0916 
0917             d->xmpMetadata()[xmpTagName].setValue(xmpTxtBag.get());
0918         }
0919 
0920         return true;
0921     }
0922     catch (Exiv2::AnyError& e)
0923     {
0924         d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string Bag into image with Exiv2:"), e);
0925     }
0926     catch (...)
0927     {
0928         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0929     }
0930 
0931 #else
0932 
0933     Q_UNUSED(xmpTagName);
0934     Q_UNUSED(bag);
0935 
0936 #endif // _XMP_SUPPORT_
0937 
0938     return false;
0939 }
0940 
0941 bool MetaEngine::addToXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToAdd) const
0942 {
0943     QStringList oldEntries = getXmpTagStringBag(xmpTagName, false);
0944     QStringList newEntries = entriesToAdd;
0945 
0946     // Create a list of keywords including old one which already exists.
0947 
0948     for (QStringList::const_iterator it = oldEntries.constBegin() ; it != oldEntries.constEnd() ; ++it)
0949     {
0950         if (!newEntries.contains(*it))
0951         {
0952             newEntries.append(*it);
0953         }
0954     }
0955 
0956     if (setXmpTagStringBag(xmpTagName, newEntries))
0957     {
0958         return true;
0959     }
0960 
0961     return false;
0962 }
0963 
0964 bool MetaEngine::removeFromXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToRemove) const
0965 {
0966     QStringList currentEntries = getXmpTagStringBag(xmpTagName, false);
0967     QStringList newEntries;
0968 
0969     // Create a list of current keywords except those that shall be removed
0970 
0971     for (QStringList::const_iterator it = currentEntries.constBegin() ; it != currentEntries.constEnd() ; ++it)
0972     {
0973         if (!entriesToRemove.contains(*it))
0974         {
0975             newEntries.append(*it);
0976         }
0977     }
0978 
0979     if (setXmpTagStringBag(xmpTagName, newEntries))
0980     {
0981         return true;
0982     }
0983 
0984     return false;
0985 }
0986 
0987 QVariant MetaEngine::getXmpTagVariant(const char* xmpTagName, bool rationalAsListOfInts, bool stringEscapeCR) const
0988 {
0989 
0990 #ifdef _XMP_SUPPORT_
0991 
0992     try
0993     {
0994         Exiv2::XmpData xmpData(d->xmpMetadata());
0995         Exiv2::XmpKey key(xmpTagName);
0996         Exiv2::XmpData::const_iterator it = xmpData.findKey(key);
0997 
0998         if (it != xmpData.end())
0999         {
1000             switch (it->typeId())
1001             {
1002                 case Exiv2::unsignedByte:
1003                 case Exiv2::unsignedShort:
1004                 case Exiv2::unsignedLong:
1005                 case Exiv2::signedShort:
1006                 case Exiv2::signedLong:
1007                 {
1008 #if EXIV2_TEST_VERSION(0,27,99)
1009                     return QVariant((int)it->toInt64());
1010 #else
1011                     return QVariant((int)it->toLong());
1012 #endif
1013                 }
1014 
1015                 case Exiv2::unsignedRational:
1016                 case Exiv2::signedRational:
1017                 {
1018                     if (rationalAsListOfInts)
1019                     {
1020                         if (!(*it).count())
1021                         {
1022 
1023 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1024 
1025                             return QVariant(QMetaType(QMetaType::QVariantList));
1026 
1027 #else
1028 
1029                             return QVariant(QVariant::List);
1030 
1031 #endif
1032 
1033                         }
1034 
1035                         QList<QVariant> list;
1036                         list << (*it).toRational().first;
1037                         list << (*it).toRational().second;
1038 
1039                         return QVariant(list);
1040                     }
1041                     else
1042                     {
1043                         if (!(*it).count())
1044                         {
1045 
1046 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1047 
1048                             return QVariant(QMetaType(QMetaType::Double));
1049 
1050 #else
1051 
1052                             return QVariant(QVariant::Double);
1053 
1054 #endif
1055 
1056                         }
1057 
1058                         // prefer double precision
1059 
1060                         double num = (*it).toRational().first;
1061                         double den = (*it).toRational().second;
1062 
1063                         if (den == 0.0)
1064                         {
1065 
1066 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1067 
1068                             return QVariant(QMetaType(QMetaType::Double));
1069 
1070 #else
1071 
1072                             return QVariant(QVariant::Double);
1073 
1074 #endif
1075 
1076                         }
1077 
1078                         return QVariant(num / den);
1079                     }
1080                 }
1081 
1082                 case Exiv2::date:
1083                 case Exiv2::time:
1084                 {
1085                     QDateTime dateTime = QDateTime::fromString(QString::fromStdString(it->toString()), Qt::ISODate);
1086                     dateTime.setTimeSpec(Qt::UTC);
1087 
1088                     return QVariant(dateTime);
1089                 }
1090 
1091                 case Exiv2::asciiString:
1092                 case Exiv2::comment:
1093                 case Exiv2::string:
1094                 {
1095                     std::ostringstream os;
1096                     os << *it;
1097                     QString tagValue = QString::fromStdString(os.str());
1098 
1099                     if (stringEscapeCR)
1100                     {
1101                         tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
1102                     }
1103 
1104                     return QVariant(tagValue);
1105                 }
1106 
1107                 case Exiv2::xmpText:
1108                 {
1109                     std::ostringstream os;
1110                     os << *it;
1111                     QString tagValue = QString::fromStdString(os.str());
1112 
1113                     if (stringEscapeCR)
1114                     {
1115                         tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
1116                     }
1117 
1118                     return tagValue;
1119                 }
1120 
1121                 case Exiv2::xmpBag:
1122                 case Exiv2::xmpSeq:
1123                 case Exiv2::xmpAlt:
1124                 {
1125                     QStringList list;
1126 
1127                     for (int i = 0 ; i < (int)it->count() ; ++i)
1128                     {
1129                         list << QString::fromStdString(it->toString(i));
1130                     }
1131 
1132                     return list;
1133                 }
1134 
1135                 case Exiv2::langAlt:
1136                 {
1137                     // access the value directly
1138 
1139                     const Exiv2::LangAltValue &value = static_cast<const Exiv2::LangAltValue &>(it->value());
1140                     QMap<QString, QVariant> map;
1141 
1142                     // access the ValueType std::map< std::string, std::string>
1143 
1144                     Exiv2::LangAltValue::ValueType::const_iterator i;
1145 
1146                     for (i = value.value_.begin() ; i != value.value_.end() ; ++i)
1147                     {
1148                         map[QString::fromUtf8(i->first.c_str())] = QString::fromUtf8(i->second.c_str());
1149                     }
1150 
1151                     return map;
1152                 }
1153 
1154                 default:
1155                 {
1156                     break;
1157                 }
1158             }
1159         }
1160     }
1161     catch (Exiv2::AnyError& e)
1162     {
1163         d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image with Exiv2:")
1164                                     .arg(QLatin1String(xmpTagName)), e);
1165     }
1166     catch (...)
1167     {
1168         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1169     }
1170 
1171 #else
1172 
1173     Q_UNUSED(xmpTagName);
1174     Q_UNUSED(rationalAsListOfInts);
1175     Q_UNUSED(stringEscapeCR);
1176 
1177 #endif // _XMP_SUPPORT_
1178 
1179     return QVariant();
1180 }
1181 
1182 bool MetaEngine::registerXmpNameSpace(const QString& uri, const QString& prefix)
1183 {
1184 
1185 #ifdef _XMP_SUPPORT_
1186 
1187     try
1188     {
1189         QString ns = uri;
1190 
1191         if (!uri.endsWith(QLatin1Char('/')))
1192         {
1193             ns.append(QLatin1Char('/'));
1194         }
1195 
1196         Exiv2::XmpProperties::registerNs(ns.toLatin1().constData(), prefix.toLatin1().constData());
1197 
1198         return true;
1199     }
1200     catch (Exiv2::AnyError& e)
1201     {
1202         Private::printExiv2ExceptionError(QLatin1String("Cannot register a new Xmp namespace with Exiv2:"), e);
1203     }
1204     catch (...)
1205     {
1206         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1207     }
1208 
1209 #else
1210 
1211     Q_UNUSED(uri);
1212     Q_UNUSED(prefix);
1213 
1214 #endif // _XMP_SUPPORT_
1215 
1216     return false;
1217 }
1218 
1219 bool MetaEngine::unregisterXmpNameSpace(const QString& uri)
1220 {
1221 
1222 #ifdef _XMP_SUPPORT_
1223 
1224     try
1225     {
1226         QString ns = uri;
1227 
1228         if (!uri.endsWith(QLatin1Char('/')))
1229         {
1230             ns.append(QLatin1Char('/'));
1231         }
1232 
1233         Exiv2::XmpProperties::unregisterNs(ns.toLatin1().constData());
1234 
1235         return true;
1236     }
1237     catch (Exiv2::AnyError& e)
1238     {
1239         Private::printExiv2ExceptionError(QLatin1String("Cannot unregister a new Xmp namespace with Exiv2:"), e);
1240     }
1241     catch (...)
1242     {
1243         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1244     }
1245 
1246 #else
1247 
1248     Q_UNUSED(uri);
1249 
1250 #endif // _XMP_SUPPORT_
1251 
1252     return false;
1253 }
1254 
1255 bool MetaEngine::removeXmpTag(const char* xmpTagName, bool family) const
1256 {
1257 
1258 #ifdef _XMP_SUPPORT_
1259 
1260     try
1261     {
1262         Exiv2::XmpKey xmpKey(xmpTagName);
1263         Exiv2::XmpData::iterator it = d->xmpMetadata().findKey(xmpKey);
1264 
1265         if (it != d->xmpMetadata().end())
1266         {
1267             if (!family)
1268             {
1269                 d->xmpMetadata().erase(it);
1270             }
1271             else
1272             {
1273                 d->xmpMetadata().eraseFamily(it);
1274             }
1275 
1276             return true;
1277         }
1278     }
1279     catch (Exiv2::AnyError& e)
1280     {
1281         d->printExiv2ExceptionError(QLatin1String("Cannot remove Xmp tag with Exiv2:"), e);
1282     }
1283     catch (...)
1284     {
1285         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
1286     }
1287 
1288 #else
1289 
1290     Q_UNUSED(xmpTagName);
1291 
1292 #endif // _XMP_SUPPORT_
1293 
1294     return false;
1295 }
1296 
1297 QStringList MetaEngine::getXmpKeywords() const
1298 {
1299     return (getXmpTagStringBag("Xmp.dc.subject", false));
1300 }
1301 
1302 bool MetaEngine::setXmpKeywords(const QStringList& newKeywords) const
1303 {
1304     return addToXmpTagStringBag("Xmp.dc.subject", newKeywords);
1305 }
1306 
1307 bool MetaEngine::removeXmpKeywords(const QStringList& keywordsToRemove)
1308 {
1309     return removeFromXmpTagStringBag("Xmp.dc.subject", keywordsToRemove);
1310 }
1311 
1312 QStringList MetaEngine::getXmpSubCategories() const
1313 {
1314     return (getXmpTagStringBag("Xmp.photoshop.SupplementalCategories", false));
1315 }
1316 
1317 bool MetaEngine::setXmpSubCategories(const QStringList& newSubCategories) const
1318 {
1319     return addToXmpTagStringBag("Xmp.photoshop.SupplementalCategories", newSubCategories);
1320 }
1321 
1322 bool MetaEngine::removeXmpSubCategories(const QStringList& subCategoriesToRemove)
1323 {
1324     return removeFromXmpTagStringBag("Xmp.photoshop.SupplementalCategories", subCategoriesToRemove);
1325 }
1326 
1327 QStringList MetaEngine::getXmpSubjects() const
1328 {
1329     return (getXmpTagStringBag("Xmp.iptc.SubjectCode", false));
1330 }
1331 
1332 bool MetaEngine::setXmpSubjects(const QStringList& newSubjects) const
1333 {
1334     return addToXmpTagStringBag("Xmp.iptc.SubjectCode", newSubjects);
1335 }
1336 
1337 bool MetaEngine::removeXmpSubjects(const QStringList& subjectsToRemove)
1338 {
1339     return removeFromXmpTagStringBag("Xmp.iptc.SubjectCode", subjectsToRemove);
1340 }
1341 
1342 MetaEngine::TagsMap MetaEngine::getXmpTagsList() const
1343 {
1344     TagsMap tagsMap;
1345     d->getXMPTagsListFromPrefix(QLatin1String("acdsee"),         tagsMap);
1346     d->getXMPTagsListFromPrefix(QLatin1String("audio"),          tagsMap);
1347     d->getXMPTagsListFromPrefix(QLatin1String("aux"),            tagsMap);
1348     d->getXMPTagsListFromPrefix(QLatin1String("crs"),            tagsMap);
1349     d->getXMPTagsListFromPrefix(QLatin1String("dc"),             tagsMap);
1350     d->getXMPTagsListFromPrefix(QLatin1String("digiKam"),        tagsMap);
1351     d->getXMPTagsListFromPrefix(QLatin1String("dwc"),            tagsMap);
1352     d->getXMPTagsListFromPrefix(QLatin1String("exif"),           tagsMap);
1353     d->getXMPTagsListFromPrefix(QLatin1String("iptc"),           tagsMap);
1354     d->getXMPTagsListFromPrefix(QLatin1String("iptcExt"),        tagsMap);
1355     d->getXMPTagsListFromPrefix(QLatin1String("kipi"),           tagsMap);
1356     d->getXMPTagsListFromPrefix(QLatin1String("lr"),             tagsMap);
1357     d->getXMPTagsListFromPrefix(QLatin1String("MicrosoftPhoto"), tagsMap);
1358     d->getXMPTagsListFromPrefix(QLatin1String("MP"),             tagsMap);
1359     d->getXMPTagsListFromPrefix(QLatin1String("mwg-rs"),         tagsMap);
1360     d->getXMPTagsListFromPrefix(QLatin1String("pdf"),            tagsMap);
1361     d->getXMPTagsListFromPrefix(QLatin1String("photoshop"),      tagsMap);
1362     d->getXMPTagsListFromPrefix(QLatin1String("plus"),           tagsMap);
1363     d->getXMPTagsListFromPrefix(QLatin1String("tiff"),           tagsMap);
1364     d->getXMPTagsListFromPrefix(QLatin1String("video"),          tagsMap);
1365     d->getXMPTagsListFromPrefix(QLatin1String("xmp"),            tagsMap);
1366     d->getXMPTagsListFromPrefix(QLatin1String("xmpBJ"),          tagsMap);
1367     d->getXMPTagsListFromPrefix(QLatin1String("xmpDM"),          tagsMap);
1368     d->getXMPTagsListFromPrefix(QLatin1String("xmpMM"),          tagsMap);
1369     d->getXMPTagsListFromPrefix(QLatin1String("xmpRights"),      tagsMap);
1370     d->getXMPTagsListFromPrefix(QLatin1String("xmpTPg"),         tagsMap);
1371 
1372     return tagsMap;
1373 }
1374 
1375 } // namespace Digikam
1376 
1377 #if defined(Q_CC_CLANG)
1378 #   pragma clang diagnostic pop
1379 #endif