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 QString error;
37 int line, column;
38 QFile file(_fileName);
39 if (!document.setContent(&file, &error, &line, &column)) {
40 dbgMetaData << error << " at " << line << ", " << column << " in " << _fileName;
41 return false;
42 }
43
44 QDomElement docElem = document.documentElement();
45 if (docElem.tagName() != "schema") {
46 dbgMetaData << _fileName << ": invalid root name";
47 return false;
48 }
49
50 if (!docElem.hasAttribute("prefix")) {
51 dbgMetaData << _fileName << ": missing prefix.";
52 return false;
53 }
54
55 if (!docElem.hasAttribute("uri")) {
56 dbgMetaData << _fileName << ": missing uri.";
57 return false;
58 }
59
60 prefix = docElem.attribute("prefix");
61 uri = docElem.attribute("uri");
62 dbgMetaData << ppVar(prefix) << ppVar(uri);
63
64 QDomElement structuresElt = docElem.firstChildElement("structures");
65 if (structuresElt.isNull()) {
66 return false;
67 }
68
69 QDomElement propertiesElt = docElem.firstChildElement("properties");
70 if (propertiesElt.isNull()) {
71 return false;
72 }
73
74 parseStructures(structuresElt);
75 parseProperties(propertiesElt);
76
77 return true;
78}
79
80void Schema::Private::parseStructures(QDomElement& elt)
81{
82 Q_ASSERT(elt.tagName() == "structures");
83 dbgMetaData << "Parse structures";
84
85 QDomElement e = elt.firstChildElement();
86 for (; !e.isNull(); e = e.nextSiblingElement()) {
87 if (e.tagName() == "structure") {
88 parseStructure(e);
89 } else {
90 errMetaData << "Invalid tag: " << e.tagName() << " in structures section";
91 }
92 }
93}
94
95void Schema::Private::parseStructure(QDomElement& elt)
96{
97 Q_ASSERT(elt.tagName() == "structure");
98
99 if (!elt.hasAttribute("name")) {
100 errMetaData << "Name is required for a structure";
101 return;
102 }
103
104 QString structureName = elt.attribute("name");
105 if (structures.contains(structureName)) {
106 errMetaData << structureName << " is defined twice";
107 return;
108 }
109 dbgMetaData << "Parsing structure " << structureName;
110
111 if (!elt.hasAttribute("prefix")) {
112 errMetaData << "prefix is required for structure " << structureName;
113 return;
114 }
115
116 if (!elt.hasAttribute("uri")) {
117 errMetaData << "uri is required for structure " << structureName;
118 return;
119 }
120
121 QString structurePrefix = elt.attribute("prefix");
122 QString structureUri = elt.attribute("uri");
123 dbgMetaData << ppVar(structurePrefix) << ppVar(structureUri);
124
125 Schema* schema = new Schema(structureUri, structurePrefix);
126 QDomElement e;
127 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
128 EntryInfo info;
129 QString name;
130
131 if (!parseEltType(e, info, name, false, false)) {
132 continue;
133 }
134
135 if (schema->d->types.contains(name)) {
136 errMetaData << structureName << " already contains a field " << name;
137 continue;
138 }
139
140 schema->d->types[ name ] = info;
141 }
142
143 structures[ structureName ] = TypeInfo::Private::createStructure(schema, structureName);
144}
145
146void Schema::Private::parseProperties(QDomElement& elt)
147{
148 Q_ASSERT(elt.tagName() == "properties");
149 dbgMetaData << "Parse properties";
150
151 QDomElement e;
152 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
153 EntryInfo info;
154 QString name;
155
156 if (!parseEltType(e, info, name, false, false)) {
157 continue;
158 }
159
160 if (types.contains(name)) {
161 errMetaData << name << " already defined.";
162 continue;
163 }
164
165 types[ name ] = info;
166 }
167}
168
169bool Schema::Private::parseEltType(QDomElement &elt,
170 EntryInfo &entryInfo,
171 QString &name,
172 bool ignoreStructure,
173 bool ignoreName)
174{
175 dbgMetaData << elt.tagName() << elt.attributes().count() << name << ignoreStructure << ignoreName;
176
177 QString tagName = elt.tagName();
178 if (!ignoreName && !elt.hasAttribute("name")) {
179 errMetaData << "Missing name attribute for tag " << tagName;
180 return false;
181 }
182 name = elt.attribute("name");
183
184 // TODO parse qualifier
185 if (tagName == "integer") {
186 entryInfo.propertyType = TypeInfo::Private::Integer;
187 } else if (tagName == "boolean") {
188 entryInfo.propertyType = TypeInfo::Private::Boolean;
189 } else if (tagName == "date") {
190 entryInfo.propertyType = TypeInfo::Private::Date;
191 } else if (tagName == "text") {
192 entryInfo.propertyType = TypeInfo::Private::Text;
193 } else if (tagName == "seq") {
194 const TypeInfo* ei = parseAttType(elt, ignoreStructure);
195 if (!ei) {
196 ei = parseEmbType(elt, ignoreStructure);
197 }
198
199 if (!ei) {
200 errMetaData << "No type defined for " << name;
201 return false;
202 }
203
204 entryInfo.propertyType = TypeInfo::Private::orderedArray(ei);
205 } else if (tagName == "bag") {
206 const TypeInfo* ei = parseAttType(elt, ignoreStructure);
207 if (!ei) {
208 ei = parseEmbType(elt, ignoreStructure);
209 }
210
211 if (!ei) {
212 errMetaData << "No type defined for " << name;
213 return false;
214 }
215
216 entryInfo.propertyType = TypeInfo::Private::unorderedArray(ei);
217 } else if (tagName == "alt") {
218 const TypeInfo* ei = parseAttType(elt, ignoreStructure);
219 if (!ei) {
220 ei = parseEmbType(elt, ignoreStructure);
221 }
222
223 if (!ei) {
224 errMetaData << "No type defined for " << name;
225 return false;
226 }
227
228 entryInfo.propertyType = TypeInfo::Private::alternativeArray(ei);
229 } else if (tagName == "lang") {
230 entryInfo.propertyType = TypeInfo::Private::LangArray;
231 } else if (tagName == "rational") {
232 entryInfo.propertyType = TypeInfo::Private::Rational;
233 } else if (tagName == "gpscoordinate") {
234 entryInfo.propertyType = TypeInfo::Private::GPSCoordinate;
235 } else if (tagName == "openedchoice" || tagName == "closedchoice") {
236 entryInfo.propertyType = parseChoice(elt);
237 } else if (!ignoreStructure && structures.contains(tagName)) {
238 entryInfo.propertyType = structures.value(tagName);
239 } else {
240 errMetaData << tagName << " isn't a type.";
241 return false;
242 }
243
244 return true;
245}
246
247const TypeInfo* Schema::Private::parseAttType(QDomElement& elt, bool ignoreStructure)
248{
249 if (!elt.hasAttribute("type")) {
250 return 0;
251 }
252
253 QString type = elt.attribute("type");
254 if (type == "integer") {
255 return TypeInfo::Private::Integer;
256 } else if (type == "boolean") {
257 return TypeInfo::Private::Boolean;
258 } else if (type == "date") {
259 return TypeInfo::Private::Date;
260 } else if (type == "text") {
261 return TypeInfo::Private::Text;
262 } else if (type == "rational") {
263 return TypeInfo::Private::Rational;
264 } else if (!ignoreStructure && structures.contains(type)) {
265 return structures[type];
266 }
267
268 errMetaData << "Unsupported type: " << type << " in an attribute";
269 return nullptr;
270}
271
272const TypeInfo* Schema::Private::parseEmbType(QDomElement& elt, bool ignoreStructure)
273{
274 dbgMetaData << "Parse embedded type for " << elt.tagName();
275
276 QDomElement e;
277 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
278 QString type = e.tagName();
279 if (type == "integer") {
280 return TypeInfo::Private::Integer;
281 } else if (type == "boolean") {
282 return TypeInfo::Private::Boolean;
283 } else if (type == "date") {
284 return TypeInfo::Private::Date;
285 } else if (type == "text") {
286 return TypeInfo::Private::Text;
287 } else if (type == "openedchoice" || type == "closedchoice") {
288 return parseChoice(e);
289 } else if (!ignoreStructure && structures.contains(type)) {
290 return structures[type];
291 }
292 }
293
294 return nullptr;
295}
296
297const TypeInfo* Schema::Private::parseChoice(QDomElement& elt)
298{
299 const TypeInfo* choiceType = parseAttType(elt, true);
300 TypeInfo::PropertyType propertyType;
301 if (elt.tagName() == "openedchoice") {
302 propertyType = TypeInfo::OpenedChoice;
303 } else {
304 Q_ASSERT(elt.tagName() == "closedchoice");
305 propertyType = TypeInfo::ClosedChoice;
306 }
307
308 QDomElement e;
310 for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
311 EntryInfo info;
312 QString name;
313
314 if (!parseEltType(e, info, name, true, true)) {
315 continue;
316 }
317
318 if (!choiceType) {
319 choiceType = info.propertyType;
320 }
321
322 if (choiceType != info.propertyType) {
323 errMetaData << "All members of a choice need to be of the same type";
324 continue;
325 }
326
327 QString text = e.text();
328 QVariant var = text;
329
330 if (choiceType->propertyType() == TypeInfo::IntegerType) {
331 var = var.toInt();
332 } else if (choiceType->propertyType() == TypeInfo::DateType) {
333 // TODO: QVariant date parser isn't very good with XMP date
334 // (it doesn't support YYYY and YYYY-MM)
335 var = var.toDateTime();
336 }
337 choices.push_back(TypeInfo::Choice(Value(var), name));
338 }
339
340 return TypeInfo::Private::createChoice(propertyType, choiceType, choices);
341}
342
344 : d(new Private)
345{
346}
347
348Schema::Schema(const QString & _uri, const QString & _ns)
349 : d(new Private)
350{
351 d->uri = _uri;
352 d->prefix = _ns;
353}
354
356{
357 dbgMetaData << "Deleting schema " << d->uri << " " << d->prefix;
358 dbgMetaData.noquote() << kisBacktrace();
359 delete d;
360}
361
362const TypeInfo* Schema::propertyType(const QString& _propertyName) const
363{
364 if (d->types.contains(_propertyName)) {
365 return d->types.value(_propertyName).propertyType;
366 }
367 return 0;
368}
369
370const TypeInfo* Schema::structure(const QString& _structureName) const
371{
372 return d->structures.value(_structureName);
373}
374
375
376QString Schema::uri() const
377{
378 return d->uri;
379}
380
381QString Schema::prefix() const
382{
383 return d->prefix;
384}
385
386QString Schema::generateQualifiedName(const QString & name) const
387{
388 dbgMetaData << "generateQualifiedName for " << name;
389 Q_ASSERT(!name.isEmpty() && !name.isNull());
390 return prefix() + ':' + name;
391}
392
393QDebug operator<<(QDebug debug, const KisMetaData::Schema &c)
394{
395 debug.nospace() << "Uri = " << c.uri() << " Prefix = " << c.prefix();
396 return debug.space();
397}
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)