File indexing completed on 2024-05-19 04:27:05

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 
0035     QDomDocument document;
0036     QString error;
0037     int line, column;
0038     QFile file(_fileName);
0039     if (!document.setContent(&file, &error, &line, &column)) {
0040         dbgMetaData << error << " at " << line << ", " << column << " in " << _fileName;
0041         return false;
0042     }
0043 
0044     QDomElement docElem = document.documentElement();
0045     if (docElem.tagName() != "schema") {
0046         dbgMetaData << _fileName << ": invalid root name";
0047         return false;
0048     }
0049 
0050     if (!docElem.hasAttribute("prefix")) {
0051         dbgMetaData << _fileName << ": missing prefix.";
0052         return false;
0053     }
0054 
0055     if (!docElem.hasAttribute("uri")) {
0056         dbgMetaData << _fileName << ": missing uri.";
0057         return false;
0058     }
0059 
0060     prefix = docElem.attribute("prefix");
0061     uri = docElem.attribute("uri");
0062     dbgMetaData << ppVar(prefix) << ppVar(uri);
0063 
0064     QDomElement structuresElt = docElem.firstChildElement("structures");
0065     if (structuresElt.isNull()) {
0066         return false;
0067     }
0068 
0069     QDomElement propertiesElt = docElem.firstChildElement("properties");
0070     if (propertiesElt.isNull()) {
0071         return false;
0072     }
0073 
0074     parseStructures(structuresElt);
0075     parseProperties(propertiesElt);
0076 
0077     return true;
0078 }
0079 
0080 void Schema::Private::parseStructures(QDomElement& elt)
0081 {
0082     Q_ASSERT(elt.tagName() == "structures");
0083     dbgMetaData << "Parse structures";
0084 
0085     QDomElement e = elt.firstChildElement();
0086     for (; !e.isNull(); e = e.nextSiblingElement()) {
0087         if (e.tagName() == "structure") {
0088             parseStructure(e);
0089         } else {
0090             errMetaData << "Invalid tag: " << e.tagName() << " in structures section";
0091         }
0092     }
0093 }
0094 
0095 void Schema::Private::parseStructure(QDomElement& elt)
0096 {
0097     Q_ASSERT(elt.tagName() == "structure");
0098 
0099     if (!elt.hasAttribute("name")) {
0100         errMetaData << "Name is required for a structure";
0101         return;
0102     }
0103 
0104     QString structureName = elt.attribute("name");
0105     if (structures.contains(structureName)) {
0106         errMetaData << structureName << " is defined twice";
0107         return;
0108     }
0109     dbgMetaData << "Parsing structure " << structureName;
0110 
0111     if (!elt.hasAttribute("prefix")) {
0112         errMetaData << "prefix is required for structure " << structureName;
0113         return;
0114     }
0115 
0116     if (!elt.hasAttribute("uri")) {
0117         errMetaData << "uri is required for structure " << structureName;
0118         return;
0119     }
0120 
0121     QString structurePrefix = elt.attribute("prefix");
0122     QString structureUri = elt.attribute("uri");
0123     dbgMetaData << ppVar(structurePrefix) << ppVar(structureUri);
0124 
0125     Schema* schema = new Schema(structureUri, structurePrefix);
0126     QDomElement e;
0127     for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0128         EntryInfo info;
0129         QString name;
0130 
0131         if (!parseEltType(e, info, name, false, false)) {
0132             continue;
0133         }
0134 
0135         if (schema->d->types.contains(name)) {
0136             errMetaData << structureName << " already contains a field " << name;
0137             continue;
0138         }
0139 
0140         schema->d->types[ name ] = info;
0141     }
0142 
0143     structures[ structureName ] = TypeInfo::Private::createStructure(schema, structureName);
0144 }
0145 
0146 void Schema::Private::parseProperties(QDomElement& elt)
0147 {
0148     Q_ASSERT(elt.tagName() == "properties");
0149     dbgMetaData << "Parse properties";
0150 
0151     QDomElement e;
0152     for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0153         EntryInfo info;
0154         QString name;
0155 
0156         if (!parseEltType(e, info, name, false, false)) {
0157             continue;
0158         }
0159 
0160         if (types.contains(name)) {
0161             errMetaData << name << " already defined.";
0162             continue;
0163         }
0164 
0165         types[ name ] = info;
0166     }
0167 }
0168 
0169 bool Schema::Private::parseEltType(QDomElement &elt,
0170                                    EntryInfo &entryInfo,
0171                                    QString &name,
0172                                    bool ignoreStructure,
0173                                    bool ignoreName)
0174 {
0175     dbgMetaData << elt.tagName() << elt.attributes().count() << name << ignoreStructure << ignoreName;
0176 
0177     QString tagName = elt.tagName();
0178     if (!ignoreName && !elt.hasAttribute("name")) {
0179         errMetaData << "Missing name attribute for tag " << tagName;
0180         return false;
0181     }
0182     name = elt.attribute("name");
0183 
0184     // TODO parse qualifier
0185     if (tagName == "integer") {
0186         entryInfo.propertyType = TypeInfo::Private::Integer;
0187     } else if (tagName == "boolean") {
0188         entryInfo.propertyType = TypeInfo::Private::Boolean;
0189     } else if (tagName == "date") {
0190         entryInfo.propertyType = TypeInfo::Private::Date;
0191     } else if (tagName == "text") {
0192         entryInfo.propertyType = TypeInfo::Private::Text;
0193     } else if (tagName == "seq") {
0194         const TypeInfo* ei = parseAttType(elt, ignoreStructure);
0195         if (!ei) {
0196             ei = parseEmbType(elt, ignoreStructure);
0197         }
0198 
0199         if (!ei) {
0200             errMetaData << "No type defined for " << name;
0201             return false;
0202         }
0203 
0204         entryInfo.propertyType = TypeInfo::Private::orderedArray(ei);
0205     } else if (tagName == "bag") {
0206         const TypeInfo* ei = parseAttType(elt, ignoreStructure);
0207         if (!ei) {
0208             ei = parseEmbType(elt, ignoreStructure);
0209         }
0210 
0211         if (!ei) {
0212             errMetaData << "No type defined for " << name;
0213             return false;
0214         }
0215 
0216         entryInfo.propertyType = TypeInfo::Private::unorderedArray(ei);
0217     } else if (tagName == "alt") {
0218         const TypeInfo* ei = parseAttType(elt, ignoreStructure);
0219         if (!ei) {
0220             ei = parseEmbType(elt, ignoreStructure);
0221         }
0222 
0223         if (!ei) {
0224             errMetaData << "No type defined for " << name;
0225             return false;
0226         }
0227 
0228         entryInfo.propertyType = TypeInfo::Private::alternativeArray(ei);
0229     } else if (tagName == "lang") {
0230         entryInfo.propertyType = TypeInfo::Private::LangArray;
0231     } else if (tagName == "rational") {
0232         entryInfo.propertyType = TypeInfo::Private::Rational;
0233     } else if (tagName == "gpscoordinate") {
0234         entryInfo.propertyType = TypeInfo::Private::GPSCoordinate;
0235     } else if (tagName == "openedchoice" || tagName == "closedchoice") {
0236         entryInfo.propertyType = parseChoice(elt);
0237     } else if (!ignoreStructure && structures.contains(tagName)) {
0238         entryInfo.propertyType = structures.value(tagName);
0239     } else {
0240         errMetaData << tagName << " isn't a type.";
0241         return false;
0242     }
0243 
0244     return true;
0245 }
0246 
0247 const TypeInfo* Schema::Private::parseAttType(QDomElement& elt, bool ignoreStructure)
0248 {
0249     if (!elt.hasAttribute("type")) {
0250         return 0;
0251     }
0252 
0253     QString type = elt.attribute("type");
0254     if (type == "integer") {
0255         return TypeInfo::Private::Integer;
0256     } else if (type == "boolean") {
0257         return TypeInfo::Private::Boolean;
0258     } else if (type == "date") {
0259         return TypeInfo::Private::Date;
0260     } else if (type == "text") {
0261         return TypeInfo::Private::Text;
0262     } else if (type == "rational") {
0263         return TypeInfo::Private::Rational;
0264     } else if (!ignoreStructure && structures.contains(type)) {
0265         return structures[type];
0266     }
0267 
0268     errMetaData << "Unsupported type: " << type << " in an attribute";
0269     return nullptr;
0270 }
0271 
0272 const TypeInfo* Schema::Private::parseEmbType(QDomElement& elt, bool ignoreStructure)
0273 {
0274     dbgMetaData << "Parse embedded type for " << elt.tagName();
0275 
0276     QDomElement e;
0277     for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0278         QString type = e.tagName();
0279         if (type == "integer") {
0280             return TypeInfo::Private::Integer;
0281         } else if (type == "boolean") {
0282             return TypeInfo::Private::Boolean;
0283         } else if (type == "date") {
0284             return TypeInfo::Private::Date;
0285         } else if (type == "text") {
0286             return TypeInfo::Private::Text;
0287         } else if (type == "openedchoice" || type == "closedchoice") {
0288             return parseChoice(e);
0289         } else if (!ignoreStructure && structures.contains(type)) {
0290             return structures[type];
0291         }
0292     }
0293 
0294     return nullptr;
0295 }
0296 
0297 const TypeInfo* Schema::Private::parseChoice(QDomElement& elt)
0298 {
0299     const TypeInfo* choiceType = parseAttType(elt, true);
0300     TypeInfo::PropertyType propertyType;
0301     if (elt.tagName() == "openedchoice") {
0302         propertyType = TypeInfo::OpenedChoice;
0303     } else {
0304         Q_ASSERT(elt.tagName() == "closedchoice");
0305         propertyType = TypeInfo::ClosedChoice;
0306     }
0307 
0308     QDomElement e;
0309     QList<TypeInfo::Choice> choices;
0310     for (e = elt.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0311         EntryInfo info;
0312         QString name;
0313 
0314         if (!parseEltType(e, info, name, true, true)) {
0315             continue;
0316         }
0317 
0318         if (!choiceType) {
0319             choiceType = info.propertyType;
0320         }
0321 
0322         if (choiceType != info.propertyType) {
0323             errMetaData << "All members of a choice need to be of the same type";
0324             continue;
0325         }
0326 
0327         QString text = e.text();
0328         QVariant var = text;
0329 
0330         if (choiceType->propertyType() == TypeInfo::IntegerType) {
0331             var = var.toInt();
0332         } else if (choiceType->propertyType() == TypeInfo::DateType) {
0333             // TODO: QVariant date parser isn't very good with XMP date
0334             // (it doesn't support YYYY and YYYY-MM)
0335             var = var.toDateTime();
0336         }
0337         choices.push_back(TypeInfo::Choice(Value(var), name));
0338     }
0339 
0340     return TypeInfo::Private::createChoice(propertyType, choiceType, choices);
0341 }
0342 
0343 Schema::Schema()
0344         : d(new Private)
0345 {
0346 }
0347 
0348 Schema::Schema(const QString & _uri, const QString & _ns)
0349         : d(new Private)
0350 {
0351     d->uri = _uri;
0352     d->prefix = _ns;
0353 }
0354 
0355 Schema::~Schema()
0356 {
0357     dbgMetaData << "Deleting schema " << d->uri << " " << d->prefix;
0358     dbgMetaData.noquote() << kisBacktrace();
0359     delete d;
0360 }
0361 
0362 const TypeInfo* Schema::propertyType(const QString& _propertyName) const
0363 {
0364     if (d->types.contains(_propertyName)) {
0365         return d->types.value(_propertyName).propertyType;
0366     }
0367     return 0;
0368 }
0369 
0370 const TypeInfo* Schema::structure(const QString& _structureName) const
0371 {
0372     return d->structures.value(_structureName);
0373 }
0374 
0375 
0376 QString Schema::uri() const
0377 {
0378     return d->uri;
0379 }
0380 
0381 QString Schema::prefix() const
0382 {
0383     return d->prefix;
0384 }
0385 
0386 QString Schema::generateQualifiedName(const QString & name) const
0387 {
0388     dbgMetaData << "generateQualifiedName for " << name;
0389     Q_ASSERT(!name.isEmpty() && !name.isNull());
0390     return prefix() + ':' + name;
0391 }
0392 
0393 QDebug operator<<(QDebug debug, const KisMetaData::Schema &c)
0394 {
0395     debug.nospace() << "Uri = " << c.uri() << " Prefix = " << c.prefix();
0396     return debug.space();
0397 }