Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_meta_data_schema.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007, 2009 Cyrille Berger <cberger@cberger.net>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6
8
9#include <QDateTime>
10#include <QDomDocument>
11#include <QFile>
12#include <QString>
13#include <QVariant>
14
17#include "kis_meta_data_value.h"
18
19using namespace KisMetaData;
20
21const QString Schema::TIFFSchemaUri = "http://ns.adobe.com/tiff/1.0/";
22const QString Schema::EXIFSchemaUri = "http://ns.adobe.com/exif/1.0/";
23const QString Schema::DublinCoreSchemaUri = "http://purl.org/dc/elements/1.1/";
24const QString Schema::XMPSchemaUri = "http://ns.adobe.com/xap/1.0/";
25const QString Schema::XMPRightsSchemaUri = "http://ns.adobe.com/xap/1.0/rights/";
26const QString Schema::XMPMediaManagementUri = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#";
27const QString Schema::MakerNoteSchemaUri = "http://www.calligra.org/krita/xmp/MakerNote/1.0/";
28const QString Schema::IPTCSchemaUri = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";
29const QString Schema::PhotoshopSchemaUri = "http://ns.adobe.com/photoshop/1.0/";
30
31bool Schema::Private::load(const QString& _fileName)
32{
33 dbgMetaData << "Loading from " << _fileName;
34
35 QDomDocument document;
36 QFile file(_fileName);
37#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
38 QString error;
39 int line, column;
40 if (!document.setContent(&file, &error, &line, &column)) {
41 dbgMetaData << error << " at " << line << ", " << column << " in " << _fileName;
42#else
43 QDomDocument::ParseResult result = document.setContent(&file);
44 if (!result) {
45 dbgMetaData << result.errorMessage << " at " << result.errorLine << ", " << result.errorColumn << " in " << _fileName;
46#endif
47 return false;
48 }
49
50 QDomElement docElem = document.documentElement();
51 if (docElem.tagName() != "schema") {
52 dbgMetaData << _fileName << ": invalid root name";
53 return false;
54 }
55
56 if (!docElem.hasAttribute("prefix")) {
57 dbgMetaData << _fileName << ": missing prefix.";
58 return false;
59 }
60
61 if (!docElem.hasAttribute("uri")) {
62 dbgMetaData << _fileName << ": missing uri.";
63 return false;
64 }
65
66 prefix = docElem.attribute("prefix");
67 uri = docElem.attribute("uri");
68 dbgMetaData << ppVar(prefix) << ppVar(uri);
69
70 QDomElement structuresElt = docElem.firstChildElement("structures");
71 if (structuresElt.isNull()) {
72 return false;
73 }
74
75 QDomElement propertiesElt = docElem.firstChildElement("properties");
76 if (propertiesElt.isNull()) {
77 return false;
78 }
79
80 parseStructures(structuresElt);
81 parseProperties(propertiesElt);
82
83 return true;
84}
85
86void Schema::Private::parseStructures(QDomElement& elt)
87{
88 Q_ASSERT(elt.tagName() == "structures");
89 dbgMetaData << "Parse structures";
90
91 QDomElement e = elt.firstChildElement();
92 for (; !e.isNull(); e = e.nextSiblingElement()) {
93 if (e.tagName() == "structure") {
94 parseStructure(e);
95 } else {
96 errMetaData << "Invalid tag: " << e.tagName() << " in structures section";
97 }
98 }
99}
100
101void Schema::Private::parseStructure(QDomElement& elt)
102{
103 Q_ASSERT(elt.tagName() == "structure");
104
105 if (!elt.hasAttribute("name")) {
106 errMetaData << "Name is required for a structure";
107 return;
108 }
109
110 QString structureName = elt.attribute("name");
111 if (structures.contains(structureName)) {
112 errMetaData << structureName << " is defined twice";
113 return;
114 }
115 dbgMetaData << "Parsing structure " << structureName;
116
117 if (!elt.hasAttribute("prefix")) {
118 errMetaData << "prefix is required for structure " << structureName;
119 return;
120 }
121
122 if (!elt.hasAttribute("uri")) {
123 errMetaData << "uri is required for structure " << structureName;
124 return;
125 }
126
127 QString structurePrefix = elt.attribute("prefix");
128 QString structureUri = elt.attribute("uri");
129 dbgMetaData << ppVar(structurePrefix) << ppVar(structureUri);
130
131 Schema* schema = new Schema(structureUri, structurePrefix);
132 QDomElement e;
133 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
134 EntryInfo info;
135 QString name;
136
137 if (!parseEltType(e, info, name, false, false)) {
138 continue;
139 }
140
141 if (schema->d->types.contains(name)) {
142 errMetaData << structureName << " already contains a field " << name;
143 continue;
144 }
145
146 schema->d->types[ name ] = info;
147 }
148
149 structures[ structureName ] = TypeInfo::Private::createStructure(schema, structureName);
150}
151
152void Schema::Private::parseProperties(QDomElement& elt)
153{
154 Q_ASSERT(elt.tagName() == "properties");
155 dbgMetaData << "Parse properties";
156
157 QDomElement e;
158 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
159 EntryInfo info;
160 QString name;
161
162 if (!parseEltType(e, info, name, false, false)) {
163 continue;
164 }
165
166 if (types.contains(name)) {
167 errMetaData << name << " already defined.";
168 continue;
169 }
170
171 types[ name ] = info;
172 }
173}
174
175bool Schema::Private::parseEltType(QDomElement &elt,
176 EntryInfo &entryInfo,
177 QString &name,
178 bool ignoreStructure,
179 bool ignoreName)
180{
181 dbgMetaData << elt.tagName() << elt.attributes().count() << name << ignoreStructure << ignoreName;
182
183 QString tagName = elt.tagName();
184 if (!ignoreName && !elt.hasAttribute("name")) {
185 errMetaData << "Missing name attribute for tag " << tagName;
186 return false;
187 }
188 name = elt.attribute("name");
189
190 // TODO parse qualifier
191 if (tagName == "integer") {
192 entryInfo.propertyType = TypeInfo::Private::Integer;
193 } else if (tagName == "boolean") {
194 entryInfo.propertyType = TypeInfo::Private::Boolean;
195 } else if (tagName == "date") {
196 entryInfo.propertyType = TypeInfo::Private::Date;
197 } else if (tagName == "text") {
198 entryInfo.propertyType = TypeInfo::Private::Text;
199 } else if (tagName == "seq") {
200 const TypeInfo* ei = parseAttType(elt, ignoreStructure);
201 if (!ei) {
202 ei = parseEmbType(elt, ignoreStructure);
203 }
204
205 if (!ei) {
206 errMetaData << "No type defined for " << name;
207 return false;
208 }
209
210 entryInfo.propertyType = TypeInfo::Private::orderedArray(ei);
211 } else if (tagName == "bag") {
212 const TypeInfo* ei = parseAttType(elt, ignoreStructure);
213 if (!ei) {
214 ei = parseEmbType(elt, ignoreStructure);
215 }
216
217 if (!ei) {
218 errMetaData << "No type defined for " << name;
219 return false;
220 }
221
222 entryInfo.propertyType = TypeInfo::Private::unorderedArray(ei);
223 } else if (tagName == "alt") {
224 const TypeInfo* ei = parseAttType(elt, ignoreStructure);
225 if (!ei) {
226 ei = parseEmbType(elt, ignoreStructure);
227 }
228
229 if (!ei) {
230 errMetaData << "No type defined for " << name;
231 return false;
232 }
233
234 entryInfo.propertyType = TypeInfo::Private::alternativeArray(ei);
235 } else if (tagName == "lang") {
236 entryInfo.propertyType = TypeInfo::Private::LangArray;
237 } else if (tagName == "rational") {
238 entryInfo.propertyType = TypeInfo::Private::Rational;
239 } else if (tagName == "gpscoordinate") {
240 entryInfo.propertyType = TypeInfo::Private::GPSCoordinate;
241 } else if (tagName == "openedchoice" || tagName == "closedchoice") {
242 entryInfo.propertyType = parseChoice(elt);
243 } else if (!ignoreStructure && structures.contains(tagName)) {
244 entryInfo.propertyType = structures.value(tagName);
245 } else {
246 errMetaData << tagName << " isn't a type.";
247 return false;
248 }
249
250 return true;
251}
252
253const TypeInfo* Schema::Private::parseAttType(QDomElement& elt, bool ignoreStructure)
254{
255 if (!elt.hasAttribute("type")) {
256 return 0;
257 }
258
259 QString type = elt.attribute("type");
260 if (type == "integer") {
261 return TypeInfo::Private::Integer;
262 } else if (type == "boolean") {
263 return TypeInfo::Private::Boolean;
264 } else if (type == "date") {
265 return TypeInfo::Private::Date;
266 } else if (type == "text") {
267 return TypeInfo::Private::Text;
268 } else if (type == "rational") {
269 return TypeInfo::Private::Rational;
270 } else if (!ignoreStructure && structures.contains(type)) {
271 return structures[type];
272 }
273
274 errMetaData << "Unsupported type: " << type << " in an attribute";
275 return nullptr;
276}
277
278const TypeInfo* Schema::Private::parseEmbType(QDomElement& elt, bool ignoreStructure)
279{
280 dbgMetaData << "Parse embedded type for " << elt.tagName();
281
282 QDomElement e;
283 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
284 QString type = e.tagName();
285 if (type == "integer") {
286 return TypeInfo::Private::Integer;
287 } else if (type == "boolean") {
288 return TypeInfo::Private::Boolean;
289 } else if (type == "date") {
290 return TypeInfo::Private::Date;
291 } else if (type == "text") {
292 return TypeInfo::Private::Text;
293 } else if (type == "openedchoice" || type == "closedchoice") {
294 return parseChoice(e);
295 } else if (!ignoreStructure && structures.contains(type)) {
296 return structures[type];
297 }
298 }
299
300 return nullptr;
301}
302
303const TypeInfo* Schema::Private::parseChoice(QDomElement& elt)
304{
305 const TypeInfo* choiceType = parseAttType(elt, true);
306 TypeInfo::PropertyType propertyType;
307 if (elt.tagName() == "openedchoice") {
308 propertyType = TypeInfo::OpenedChoice;
309 } else {
310 Q_ASSERT(elt.tagName() == "closedchoice");
311 propertyType = TypeInfo::ClosedChoice;
312 }
313
314 QDomElement e;
316 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
317 EntryInfo info;
318 QString name;
319
320 if (!parseEltType(e, info, name, true, true)) {
321 continue;
322 }
323
324 if (!choiceType) {
325 choiceType = info.propertyType;
326 }
327
328 if (choiceType != info.propertyType) {
329 errMetaData << "All members of a choice need to be of the same type";
330 continue;
331 }
332
333 QString text = e.text();
334 QVariant var = text;
335
336 if (choiceType->propertyType() == TypeInfo::IntegerType) {
337 var = var.toInt();
338 } else if (choiceType->propertyType() == TypeInfo::DateType) {
339 // TODO: QVariant date parser isn't very good with XMP date
340 // (it doesn't support YYYY and YYYY-MM)
341 var = var.toDateTime();
342 }
343 choices.push_back(TypeInfo::Choice(Value(var), name));
344 }
345
346 return TypeInfo::Private::createChoice(propertyType, choiceType, choices);
347}
348
350 : d(new Private)
351{
352}
353
354Schema::Schema(const QString & _uri, const QString & _ns)
355 : d(new Private)
356{
357 d->uri = _uri;
358 d->prefix = _ns;
359}
360
362{
363 dbgMetaData << "Deleting schema " << d->uri << " " << d->prefix;
364 dbgMetaData.noquote() << kisBacktrace();
365 delete d;
366}
367
368const TypeInfo* Schema::propertyType(const QString& _propertyName) const
369{
370 if (d->types.contains(_propertyName)) {
371 return d->types.value(_propertyName).propertyType;
372 }
373 return 0;
374}
375
376const TypeInfo* Schema::structure(const QString& _structureName) const
377{
378 return d->structures.value(_structureName);
379}
380
381
382QString Schema::uri() const
383{
384 return d->uri;
385}
386
387QString Schema::prefix() const
388{
389 return d->prefix;
390}
391
392QString Schema::generateQualifiedName(const QString & name) const
393{
394 dbgMetaData << "generateQualifiedName for " << name;
395 Q_ASSERT(!name.isEmpty() && !name.isNull());
396 return prefix() + ':' + name;
397}
398
399QDebug operator<<(QDebug debug, const KisMetaData::Schema &c)
400{
401 debug.nospace() << "Uri = " << c.uri() << " Prefix = " << c.prefix();
402 return debug.space();
403}
QDebug KRITACOMMAND_EXPORT operator<<(QDebug dbg, const KisCumulativeUndoData &data)
QString generateQualifiedName(const QString &) const
static const QString PhotoshopSchemaUri
static const QString TIFFSchemaUri
static const QString IPTCSchemaUri
const TypeInfo * structure(const QString &_structureName) const
static const QString XMPMediaManagementUri
static const QString XMPRightsSchemaUri
static const QString DublinCoreSchemaUri
const TypeInfo * propertyType(const QString &_propertyName) const
static const QString EXIFSchemaUri
static const QString XMPSchemaUri
static const QString MakerNoteSchemaUri
QString kisBacktrace()
Definition kis_debug.cpp:51
#define errMetaData
Definition kis_debug.h:123
#define ppVar(var)
Definition kis_debug.h:155
#define dbgMetaData
Definition kis_debug.h:61
const char * name(StandardAction id)