1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2006-02-23
7 * Description : item metadata interface - tags helpers.
8 *
9 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 * Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11 * Copyright (C) 2011 by Leif Huhn <leif at dkstat dot com>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 #include "dmetadata.h"
27
28 // Qt includes
29
30 #include <QLocale>
31
32 // Local includes
33
34 #include "metaenginesettings.h"
35 #include "digikam_version.h"
36 #include "digikam_globals.h"
37 #include "digikam_debug.h"
38
39 namespace Digikam
40 {
41
getItemTagsPath(QStringList & tagsPath,const DMetadataSettingsContainer & settings) const42 bool DMetadata::getItemTagsPath(QStringList& tagsPath,
43 const DMetadataSettingsContainer& settings) const
44 {
45 foreach (const NamespaceEntry& entry, settings.getReadMapping(NamespaceEntry::DM_TAG_CONTAINER()))
46 {
47 if (entry.isDisabled)
48 {
49 continue;
50 }
51
52 int index = 0;
53 QString currentNamespace = entry.namespaceName;
54 NamespaceEntry::SpecialOptions currentOpts = entry.specialOpts;
55
56 // Some namespaces have altenative paths, we must search them both
57
58 switch (entry.subspace)
59 {
60 case NamespaceEntry::XMP:
61 {
62 while (index < 2)
63 {
64 const std::string myStr = currentNamespace.toStdString();
65 const char* nameSpace = myStr.data();
66
67 switch (currentOpts)
68 {
69 case NamespaceEntry::TAG_XMPBAG:
70 {
71 tagsPath = getXmpTagStringBag(nameSpace, false);
72 break;
73 }
74
75 case NamespaceEntry::TAG_XMPSEQ:
76 {
77 tagsPath = getXmpTagStringSeq(nameSpace, false);
78 break;
79 }
80
81 case NamespaceEntry::TAG_ACDSEE:
82 {
83 getACDSeeTagsPath(tagsPath);
84 break;
85 }
86
87 // not used here, to suppress warnings
88 case NamespaceEntry::COMMENT_XMP:
89 case NamespaceEntry::COMMENT_ALTLANG:
90 case NamespaceEntry::COMMENT_ATLLANGLIST:
91 case NamespaceEntry::NO_OPTS:
92 default:
93 {
94 break;
95 }
96 }
97
98 if (!tagsPath.isEmpty())
99 {
100 if (entry.separator != QLatin1String("/"))
101 {
102 tagsPath.replaceInStrings(QLatin1String("/"), QLatin1String("\\"));
103 tagsPath.replaceInStrings(entry.separator, QLatin1String("/"));
104 }
105
106 return true;
107 }
108 else if (!entry.alternativeName.isEmpty())
109 {
110 currentNamespace = entry.alternativeName;
111 currentOpts = entry.secondNameOpts;
112 }
113 else
114 {
115 break; // no alternative namespace, go to next one
116 }
117
118 index++;
119 }
120
121 break;
122 }
123
124 case NamespaceEntry::IPTC:
125 {
126 // Try to get Tags Path list from IPTC keywords.
127 // digiKam 0.9.x has used IPTC keywords to store Tags Path list.
128 // This way is obsolete now since digiKam support XMP because IPTC
129 // do not support UTF-8 and have strings size limitation. But we will
130 // let the capability to import it for interworking issues.
131
132 tagsPath = getIptcKeywords();
133
134 if (!tagsPath.isEmpty())
135 {
136 // Work around to Imach tags path list hosted in IPTC with '.' as separator.
137
138 QStringList ntp = tagsPath.replaceInStrings(entry.separator, QLatin1String("/"));
139
140 // FIXME: The QStringList are always identical -> ntp == tagsPath.
141
142 if (ntp != tagsPath)
143 {
144 tagsPath = ntp;
145
146 //qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from Imach: " << tagsPath;
147 }
148
149 return true;
150 }
151
152 break;
153 }
154
155 case NamespaceEntry::EXIF:
156 {
157 // Try to get Tags Path list from Exif Windows keywords.
158
159 QString keyWords = getExifTagString("Exif.Image.XPKeywords", false);
160
161 if (!keyWords.isEmpty())
162 {
163 tagsPath = keyWords.split(entry.separator);
164
165 if (!tagsPath.isEmpty())
166 {
167 return true;
168 }
169 }
170
171 break;
172 }
173
174 default:
175 {
176 break;
177 }
178 }
179 }
180
181 return false;
182 }
183
setItemTagsPath(const QStringList & tagsPath,const DMetadataSettingsContainer & settings) const184 bool DMetadata::setItemTagsPath(const QStringList& tagsPath, const DMetadataSettingsContainer& settings) const
185 {
186 // NOTE : with digiKam 0.9.x, we have used IPTC Keywords for that.
187 // Now this way is obsolete, and we use XMP instead.
188
189 // Set the new Tags path list. This is set, not add-to like setXmpKeywords.
190 // Unlike the other keyword fields, we do not need to merge existing entries.
191
192 QList<NamespaceEntry> toWrite = settings.getReadMapping(NamespaceEntry::DM_TAG_CONTAINER());
193
194 if (!settings.unifyReadWrite())
195 {
196 toWrite = settings.getWriteMapping(NamespaceEntry::DM_TAG_CONTAINER());
197 }
198
199 for (const NamespaceEntry& entry : qAsConst(toWrite))
200 {
201 if (entry.isDisabled)
202 {
203 continue;
204 }
205
206 QStringList newList;
207
208 // Get keywords from tags path, for type tag
209
210 for (const QString& tagPath : tagsPath)
211 {
212 newList.append(tagPath.split(QLatin1Char('/')).last());
213 }
214
215 switch (entry.subspace)
216 {
217 case NamespaceEntry::XMP:
218 {
219 if (!supportXmp())
220 {
221 continue;
222 }
223
224 if (entry.tagPaths != NamespaceEntry::TAG)
225 {
226 newList = tagsPath;
227
228 if (entry.separator != QLatin1String("/"))
229 {
230 newList.replaceInStrings(QLatin1String("/"), entry.separator);
231 }
232 }
233
234 const std::string myStr = entry.namespaceName.toStdString();
235 const char* nameSpace = myStr.data();
236
237 switch (entry.specialOpts)
238 {
239 case NamespaceEntry::TAG_XMPSEQ:
240 {
241 if (!setXmpTagStringSeq(nameSpace, newList))
242 {
243 qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace;
244 return false;
245 }
246
247 break;
248 }
249
250 case NamespaceEntry::TAG_XMPBAG:
251 {
252
253 if (!setXmpTagStringBag(nameSpace, newList))
254 {
255 qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace;
256 return false;
257 }
258
259 break;
260 }
261
262 case NamespaceEntry::TAG_ACDSEE:
263 {
264
265 if (!setACDSeeTagsPath(newList))
266 {
267 qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace;
268 return false;
269 }
270 }
271
272 default:
273 {
274 break;
275 }
276 }
277
278 break;
279 }
280
281 case NamespaceEntry::IPTC:
282
283 if (entry.namespaceName == QLatin1String("Iptc.Application2.Keywords"))
284 {
285 if (!setIptcKeywords(getIptcKeywords(), newList))
286 {
287 qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << entry.namespaceName;
288 return false;
289 }
290 }
291
292 default:
293 {
294 break;
295 }
296 }
297 }
298
299 return true;
300 }
301
getACDSeeTagsPath(QStringList & tagsPath) const302 bool DMetadata::getACDSeeTagsPath(QStringList& tagsPath) const
303 {
304 // Try to get Tags Path list from ACDSee 8 Pro categories.
305
306 QString xmlACDSee = getXmpTagString("Xmp.acdsee.categories", false);
307
308 if (!xmlACDSee.isEmpty())
309 {
310 xmlACDSee.remove(QLatin1String("</Categories>"));
311 xmlACDSee.remove(QLatin1String("<Categories>"));
312 xmlACDSee.replace(QLatin1Char('/'), QLatin1Char('\\'));
313
314 QStringList xmlTags = xmlACDSee.split(QLatin1String("<Category Assigned"));
315 int category = 0;
316
317 foreach (const QString& tags, xmlTags)
318 {
319 if (!tags.isEmpty())
320 {
321 int count = tags.count(QLatin1String("<\\Category>"));
322 int length = tags.length() - (11 * count) - 5;
323
324 // cppcheck-suppress knownConditionTrueFalse
325 if (category == 0)
326 {
327 tagsPath << tags.mid(5, length);
328 }
329 else
330 {
331 tagsPath.last().append(QLatin1Char('/') + tags.mid(5, length));
332 }
333
334 category = category - count + 1;
335
336 if ((tags.left(5) == QLatin1String("=\"1\">")) && (category > 0))
337 {
338 tagsPath << tagsPath.last().section(QLatin1Char('/'), 0, category - 1);
339 }
340 }
341 }
342
343 if (!tagsPath.isEmpty())
344 {
345 /*
346 qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from ACDSee: " << tagsPath;
347 */
348 return true;
349 }
350 }
351
352 return false;
353 }
354
setACDSeeTagsPath(const QStringList & tagsPath) const355 bool DMetadata::setACDSeeTagsPath(const QStringList& tagsPath) const
356 {
357 // Converting Tags path list to ACDSee 8 Pro categories.
358
359 const QString category(QLatin1String("<Category Assigned=\"%1\">"));
360 QStringList splitTags;
361 QStringList xmlTags;
362
363 foreach (const QString& tags, tagsPath)
364 {
365 splitTags = tags.split(QLatin1Char('/'));
366 int current = 0;
367
368 for (int index = 0 ; index < splitTags.size() ; index++)
369 {
370 int tagIndex = xmlTags.indexOf(category.arg(0) + splitTags[index]);
371
372 if (tagIndex == -1)
373 {
374 tagIndex = xmlTags.indexOf(category.arg(1) + splitTags[index]);
375 }
376
377 splitTags[index].insert(0, category.arg(index == splitTags.size() - 1 ? 1 : 0));
378
379 if (tagIndex == -1)
380 {
381 if (index == 0)
382 {
383 xmlTags << splitTags[index];
384 xmlTags << QLatin1String("</Category>");
385 current = xmlTags.size() - 1;
386 }
387 else
388 {
389 xmlTags.insert(current, splitTags[index]);
390 xmlTags.insert(current + 1, QLatin1String("</Category>"));
391 current++;
392 }
393 }
394 else
395 {
396 if (index == (splitTags.size() - 1))
397 {
398 xmlTags[tagIndex] = splitTags[index];
399 }
400
401 current = tagIndex + 1;
402 }
403 }
404 }
405
406 QString xmlACDSee = QLatin1String("<Categories>") + xmlTags.join(QLatin1String("")) + QLatin1String("</Categories>");
407 /*
408 qCDebug(DIGIKAM_METAENGINE_LOG) << "xmlACDSee" << xmlACDSee;
409 */
410 removeXmpTag("Xmp.acdsee.categories");
411
412 if (!xmlTags.isEmpty())
413 {
414 if (!setXmpTagString("Xmp.acdsee.categories", xmlACDSee))
415 {
416 return false;
417 }
418 }
419
420 return true;
421 }
422
423 } // namespace Digikam
424