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 }