File indexing completed on 2024-05-12 15:59:16

0001 /*
0002  *  SPDX-FileCopyrightText: 2007, 2009 Cyrille Berger <cberger@cberger.net>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "kis_meta_data_schema.h"
0008 
0009 #include <QDateTime>
0010 #include <QDomDocument>
0011 #include <QFile>
0012 #include <QString>
0013 #include <QVariant>
0014 
0015 #include "kis_meta_data_type_info_p.h"
0016 #include "kis_meta_data_schema_p.h"
0017 #include "kis_meta_data_value.h"
0018 
0019 using namespace KisMetaData;
0020 
0021 const QString Schema::TIFFSchemaUri = "http://ns.adobe.com/tiff/1.0/";
0022 const QString Schema::EXIFSchemaUri = "http://ns.adobe.com/exif/1.0/";
0023 const QString Schema::DublinCoreSchemaUri = "http://purl.org/dc/elements/1.1/";
0024 const QString Schema::XMPSchemaUri = "http://ns.adobe.com/xap/1.0/";
0025 const QString Schema::XMPRightsSchemaUri = "http://ns.adobe.com/xap/1.0/rights/";
0026 const QString Schema::XMPMediaManagementUri = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#";
0027 const QString Schema::MakerNoteSchemaUri = "http://www.calligra.org/krita/xmp/MakerNote/1.0/";
0028 const QString Schema::IPTCSchemaUri = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";
0029 const QString Schema::PhotoshopSchemaUri = "http://ns.adobe.com/photoshop/1.0/";
0030 
0031 bool Schema::Private::load(const QString& _fileName)
0032 {
0033     dbgMetaData << "Loading from " << _fileName;
0034     QDomDocument document;
0035     QString error;
0036     int ligne, column;
0037     QFile file(_fileName);
0038     if (document.setContent(&file, &error, &ligne, &column)) {
0039         QDomElement docElem = document.documentElement();
0040         if (docElem.tagName() != "schema") {
0041             dbgMetaData << _fileName << ": invalid root name";
0042             return false;
0043         }
0044         if (!docElem.hasAttribute("prefix")) {
0045             dbgMetaData << _fileName << ": missing prefix.";
0046             return false;
0047         }
0048         if (!docElem.hasAttribute("uri")) {
0049             dbgMetaData << _fileName << ": missing uri.";
0050             return false;
0051         }
0052         prefix = docElem.attribute("prefix");
0053         uri = docElem.attribute("uri");
0054         dbgMetaData << ppVar(prefix) << ppVar(uri);
0055         QDomNode n = docElem.firstChild();
0056         while (!n.isNull()) {
0057             QDomElement e = n.toElement();
0058             if (!e.isNull()) {
0059                 if (e.tagName() == "structures") {
0060                     parseStructures(e);
0061                 } else if (e.tagName() == "properties") {
0062                     parseProperties(e);
0063                 }
0064             }
0065             n = n.nextSibling();
0066         }
0067         return true;
0068     } else {
0069         dbgMetaData << error << " at " << ligne << ", " << column << " in " << _fileName;
0070         return false;
0071     }
0072 }
0073 
0074 void Schema::Private::parseStructures(QDomElement& elt)
0075 {
0076     Q_ASSERT(elt.tagName() == "structures");
0077     dbgMetaData << "Parse structures";
0078     QDomNode n = elt.firstChild();
0079     while (!n.isNull()) {
0080         QDomElement e = n.toElement();
0081         if (!e.isNull()) {
0082             if (e.tagName() == "structure") {
0083                 parseStructure(e);
0084             } else {
0085                 errMetaData << "Invalid tag: " << e.tagName() << " in structures section";
0086             }
0087         }
0088         n = n.nextSibling();
0089     }
0090 }
0091 
0092 void Schema::Private::parseStructure(QDomElement& elt)
0093 {
0094     Q_ASSERT(elt.tagName() == "structure");
0095     if (!elt.hasAttribute("name")) {
0096         errMetaData << "Name is required for a structure";
0097         return;
0098     }
0099     QString structureName = elt.attribute("name");
0100     if (structures.contains(structureName)) {
0101         errMetaData << structureName << " is defined twice";
0102         return;
0103     }
0104     dbgMetaData << "Parsing structure " << structureName;
0105     if (!elt.hasAttribute("prefix")) {
0106         errMetaData << "prefix is required for structure " << structureName;
0107         return;
0108     }
0109     if (!elt.hasAttribute("uri")) {
0110         errMetaData << "uri is required for structure " << structureName;
0111         return;
0112     }
0113     QString structurePrefix = elt.attribute("prefix");
0114     QString structureUri = elt.attribute("uri");
0115     dbgMetaData << ppVar(structurePrefix) << ppVar(structureUri);
0116     Schema* schema = new Schema(structureUri, structurePrefix);
0117     QDomNode n = elt.firstChild();
0118     while (!n.isNull()) {
0119         QDomElement e = n.toElement();
0120         if (!e.isNull()) {
0121             EntryInfo info;
0122             QString name;
0123             if (parseEltType(e, info, name, false, false)) {
0124                 if (schema->d->types.contains(name)) {
0125                     errMetaData << structureName << " already contains a field " << name;
0126                 } else {
0127                     schema->d->types[ name ] = info;
0128                 }
0129             }
0130         }
0131         n = n.nextSibling();
0132     }
0133     structures[ structureName ] = TypeInfo::Private::createStructure(schema, structureName);
0134 }
0135 
0136 void Schema::Private::parseProperties(QDomElement& elt)
0137 {
0138     Q_ASSERT(elt.tagName() == "properties");
0139     dbgMetaData << "Parse properties";
0140     QDomNode n = elt.firstChild();
0141     while (!n.isNull()) {
0142         QDomElement e = n.toElement();
0143         if (!e.isNull()) {
0144             EntryInfo info;
0145             QString name;
0146             if (parseEltType(e, info, name, false, false)) {
0147                 if (types.contains(name)) {
0148                     errMetaData << name << " already defined.";
0149                 } else {
0150                     types[ name ] = info;
0151                 }
0152             }
0153         }
0154         n = n.nextSibling();
0155     }
0156 }
0157 
0158 bool Schema::Private::parseEltType(QDomElement& elt, EntryInfo& entryInfo, QString& name, bool ignoreStructure, bool ignoreName)
0159 {
0160     dbgMetaData << elt.tagName() << elt.attributes().count() << name << ignoreStructure << ignoreName;
0161     QString tagName = elt.tagName();
0162     if (!ignoreName && !elt.hasAttribute("name")) {
0163         errMetaData << "Missing name attribute for tag " << tagName;
0164         return false;
0165     }
0166     name = elt.attribute("name");
0167     // TODO parse qualifier
0168     if (tagName == "integer") {
0169         entryInfo.propertyType = TypeInfo::Private::Integer;
0170         return true;
0171     } else if (tagName == "boolean") {
0172         entryInfo.propertyType = TypeInfo::Private::Boolean;
0173         return true;
0174     } else if (tagName == "date") {
0175         entryInfo.propertyType = TypeInfo::Private::Date;
0176         return true;
0177     } else if (tagName == "text") {
0178         entryInfo.propertyType = TypeInfo::Private::Text;
0179         return true;
0180     } else if (tagName == "seq") {
0181         const TypeInfo* ei = parseAttType(elt, ignoreStructure);
0182         if (!ei) {
0183             ei = parseEmbType(elt, ignoreStructure);
0184         }
0185         if (!ei) {
0186             errMetaData << "No type defined for " << name;
0187             return false;
0188         }
0189         entryInfo.propertyType = TypeInfo::Private::orderedArray(ei);
0190         return true;
0191     } else if (tagName == "bag") {
0192         const TypeInfo* ei = parseAttType(elt, ignoreStructure);
0193         if (!ei) {
0194             ei = parseEmbType(elt, ignoreStructure);
0195         }
0196         if (!ei) {
0197             errMetaData << "No type defined for " << name;
0198             return false;
0199         }
0200         entryInfo.propertyType = TypeInfo::Private::unorderedArray(ei);
0201         return true;
0202     } else if (tagName == "alt") {
0203         const TypeInfo* ei = parseAttType(elt, ignoreStructure);
0204         if (!ei) {
0205             ei = parseEmbType(elt, ignoreStructure);
0206         }
0207         if (!ei) {
0208             errMetaData << "No type defined for " << name;
0209             return false;
0210         }
0211         entryInfo.propertyType = TypeInfo::Private::alternativeArray(ei);
0212         return true;
0213     } else if (tagName == "lang") {
0214         entryInfo.propertyType = TypeInfo::Private::LangArray;
0215         return true;
0216     } else if (tagName == "rational") {
0217         entryInfo.propertyType = TypeInfo::Private::Rational;
0218         return true;
0219     } else if (tagName == "gpscoordinate") {
0220         entryInfo.propertyType = TypeInfo::Private::GPSCoordinate;
0221         return true;
0222     } else if (tagName == "openedchoice" || tagName == "closedchoice") {
0223         entryInfo.propertyType = parseChoice(elt);
0224         return true;
0225     } else if (!ignoreStructure && structures.contains(tagName)) {
0226         entryInfo.propertyType = structures.value(tagName);
0227         return true;
0228     }
0229     errMetaData << tagName << " isn't a type.";
0230     return false;
0231 }
0232 
0233 const TypeInfo* Schema::Private::parseAttType(QDomElement& elt, bool ignoreStructure)
0234 {
0235     if (!elt.hasAttribute("type")) {
0236         return 0;
0237     }
0238     QString type = elt.attribute("type");
0239     if (type == "integer") {
0240         return TypeInfo::Private::Integer;
0241     } else if (type == "boolean") {
0242         return TypeInfo::Private::Boolean;
0243     } else if (type == "date") {
0244         return TypeInfo::Private::Date;
0245     } else if (type == "text") {
0246         return TypeInfo::Private::Text;
0247     } else if (type == "rational") {
0248         return TypeInfo::Private::Rational;
0249     } else if (!ignoreStructure && structures.contains(type)) {
0250         return structures[type];
0251     }
0252     errMetaData << "Unsupported type: " << type << " in an attribute";
0253     return 0;
0254 }
0255 
0256 const TypeInfo* Schema::Private::parseEmbType(QDomElement& elt, bool ignoreStructure)
0257 {
0258     dbgMetaData << "Parse embedded type for " << elt.tagName();
0259     QDomNode n = elt.firstChild();
0260     while (!n.isNull()) {
0261         QDomElement e = n.toElement();
0262         if (!e.isNull()) {
0263             QString type = e.tagName();
0264             if (type == "integer") {
0265                 return TypeInfo::Private::Integer;
0266             } else if (type == "boolean") {
0267                 return TypeInfo::Private::Boolean;
0268             } else if (type == "date") {
0269                 return TypeInfo::Private::Date;
0270             } else if (type == "text") {
0271                 return TypeInfo::Private::Text;
0272             } else if (type == "openedchoice" || type == "closedchoice") {
0273                 return parseChoice(e);
0274             } else if (!ignoreStructure && structures.contains(type)) {
0275                 return structures[type];
0276             }
0277         }
0278         n = n.nextSibling();
0279     }
0280     return 0;
0281 }
0282 
0283 const TypeInfo* Schema::Private::parseChoice(QDomElement& elt)
0284 {
0285     const TypeInfo* choiceType = parseAttType(elt, true);
0286     TypeInfo::PropertyType propertyType;
0287     if (elt.tagName() == "openedchoice") {
0288         propertyType = TypeInfo::OpenedChoice;
0289     } else {
0290         Q_ASSERT(elt.tagName() == "closedchoice");
0291         propertyType = TypeInfo::ClosedChoice;
0292     }
0293     QDomNode n = elt.firstChild();
0294     QList< TypeInfo::Choice > choices;
0295     while (!n.isNull()) {
0296         QDomElement e = n.toElement();
0297         if (!e.isNull()) {
0298             EntryInfo info;
0299             QString name;
0300             if (parseEltType(e, info, name, true, true)) {
0301                 if (! choiceType) choiceType = info.propertyType;
0302                 if (choiceType == info.propertyType) {
0303                     QString text = e.text();
0304                     QVariant var = text;
0305                     if (choiceType->propertyType() == TypeInfo::IntegerType) {
0306                         var = var.toInt();
0307                     } else if (choiceType->propertyType() == TypeInfo::DateType) { // TODO QVariant date parser isn't very good with XMP date (it doesn't support YYYY and YYYY-MM
0308                         var = var.toDateTime();
0309                     }
0310                     choices.push_back(TypeInfo::Choice(Value(var), name));
0311                 } else {
0312                     errMetaData << "All members of a choice need to be of the same type";
0313                 }
0314             }
0315         }
0316         n = n.nextSibling();
0317     }
0318     return TypeInfo::Private::createChoice(propertyType, choiceType, choices);
0319 }
0320 
0321 Schema::Schema()
0322         : d(new Private)
0323 {
0324 }
0325 
0326 Schema::Schema(const QString & _uri, const QString & _ns)
0327         : d(new Private)
0328 {
0329     d->uri = _uri;
0330     d->prefix = _ns;
0331 }
0332 
0333 Schema::~Schema()
0334 {
0335     dbgMetaData << "Deleting schema " << d->uri << " " << d->prefix;
0336     dbgMetaData.noquote() << kisBacktrace();
0337     delete d;
0338 }
0339 
0340 const TypeInfo* Schema::propertyType(const QString& _propertyName) const
0341 {
0342     if (d->types.contains(_propertyName)) {
0343         return d->types.value(_propertyName).propertyType;
0344     }
0345     return 0;
0346 }
0347 
0348 const TypeInfo* Schema::structure(const QString& _structureName) const
0349 {
0350     return d->structures.value(_structureName);
0351 }
0352 
0353 
0354 QString Schema::uri() const
0355 {
0356     return d->uri;
0357 }
0358 
0359 QString Schema::prefix() const
0360 {
0361     return d->prefix;
0362 }
0363 
0364 QString Schema::generateQualifiedName(const QString & name) const
0365 {
0366     dbgMetaData << "generateQualifiedName for " << name;
0367     Q_ASSERT(!name.isEmpty() && !name.isNull());
0368     return prefix() + ':' + name;
0369 }
0370 
0371 QDebug operator<<(QDebug debug, const KisMetaData::Schema &c)
0372 {
0373     debug.nospace() << "Uri = " << c.uri() << " Prefix = " << c.prefix();
0374     return debug.space();
0375 }