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 }