diff --git a/libs/ui/kisexiv2/kis_xmp_io.cpp b/libs/ui/kisexiv2/kis_xmp_io.cpp index c0cfae9e73..db76f25ffc 100644 --- a/libs/ui/kisexiv2/kis_xmp_io.cpp +++ b/libs/ui/kisexiv2/kis_xmp_io.cpp @@ -1,399 +1,399 @@ /* * Copyright (c) 2008-2010 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_xmp_io.h" #include #include "kis_exiv2.h" #include #include #include #include #include #include #include #include KisXMPIO::KisXMPIO() { } KisXMPIO::~KisXMPIO() { } inline std::string exiv2Prefix(const KisMetaData::Schema* _schema) { const QByteArray latin1SchemaUri = _schema->uri().toLatin1(); std::string prefix = Exiv2::XmpProperties::prefix(latin1SchemaUri.constData()); if (prefix.empty()) { dbgMetaData << "Unknown namespace " << ppVar(_schema->uri()) << ppVar(_schema->prefix()); prefix = _schema->prefix().toLatin1().constData(); Exiv2::XmpProperties::registerNs(latin1SchemaUri.constData(), prefix); } return prefix; } namespace { void saveStructure(Exiv2::XmpData& xmpData_, const QString& name, const std::string& prefix, const QMap& structure, const KisMetaData::Schema* structureSchema) { std::string structPrefix = exiv2Prefix(structureSchema); for (QMap::const_iterator it = structure.begin(); it != structure.end(); ++it) { Q_ASSERT(it.value().type() != KisMetaData::Value::Structure); // Can't nest structure QString key = QString("%1/%2:%3").arg(name).arg(structPrefix.c_str()).arg(it.key()); Exiv2::XmpKey ekey(prefix, key.toLatin1().constData()); dbgMetaData << ppVar(key) << ppVar(ekey.key().c_str()); Exiv2::Value *v = kmdValueToExivXmpValue(it.value()); if (v) { xmpData_.add(ekey, v); } } } } bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType) const { dbgMetaData << "Save XMP Data"; Exiv2::XmpData xmpData_; for (QHash::const_iterator it = store->begin(); it != store->end(); ++it) { const KisMetaData::Entry& entry = *it; // Check whether the prefix and namespace are know to exiv2 std::string prefix = exiv2Prefix(entry.schema()); dbgMetaData << "Saving " << entry.name(); const KisMetaData::Value& value = entry.value(); const KisMetaData::TypeInfo* typeInfo = entry.schema()->propertyType(entry.name()); if (value.type() == KisMetaData::Value::Structure) { QMap structure = value.asStructure(); const KisMetaData::Schema* structureSchema = 0; if (typeInfo) { structureSchema = typeInfo->structureSchema(); } if (!structureSchema) { dbgMetaData << "Unknown schema for " << entry.name(); structureSchema = entry.schema(); } Q_ASSERT(structureSchema); saveStructure(xmpData_, entry.name(), prefix, structure, structureSchema); } else { Exiv2::XmpKey key(prefix, entry.name().toLatin1().constData()); if (typeInfo && (typeInfo->propertyType() == KisMetaData::TypeInfo::OrderedArrayType || typeInfo->propertyType() == KisMetaData::TypeInfo::UnorderedArrayType || typeInfo->propertyType() == KisMetaData::TypeInfo::AlternativeArrayType) && typeInfo->embeddedPropertyType()->propertyType() == KisMetaData::TypeInfo::StructureType) { // Here is the bad part, again we need to do it by hand Exiv2::XmpTextValue tv; switch (typeInfo->propertyType()) { case KisMetaData::TypeInfo::OrderedArrayType: tv.setXmpArrayType(Exiv2::XmpValue::xaSeq); break; case KisMetaData::TypeInfo::UnorderedArrayType: tv.setXmpArrayType(Exiv2::XmpValue::xaBag); break; case KisMetaData::TypeInfo::AlternativeArrayType: tv.setXmpArrayType(Exiv2::XmpValue::xaAlt); break; default: // Cannot happen ; } xmpData_.add(key, &tv); // set the arrya type const KisMetaData::TypeInfo* stuctureTypeInfo = typeInfo->embeddedPropertyType(); const KisMetaData::Schema* structureSchema = 0; if (stuctureTypeInfo) { structureSchema = stuctureTypeInfo->structureSchema(); } if (!structureSchema) { dbgMetaData << "Unknown schema for " << entry.name(); structureSchema = entry.schema(); } Q_ASSERT(structureSchema); QList array = value.asArray(); - for (int idx = 0; idx < array.size(); ++idx) { + for (int idx = 0; idx < array.count(); ++idx) { saveStructure(xmpData_, QString("%1[%2]").arg(entry.name()).arg(idx + 1), prefix, array[idx].asStructure(), structureSchema); } } else { dbgMetaData << ppVar(key.key().c_str()); Exiv2::Value *v = kmdValueToExivXmpValue(value); if (v) { xmpData_.add(key, v); } } } // TODO property qualifier } // Serialize data std::string xmpPacket_; Exiv2::XmpParser::encode(xmpPacket_, xmpData_); // Save data into the IO device ioDevice->open(QIODevice::WriteOnly); if (headerType == KisMetaData::IOBackend::JpegHeader) { xmpPacket_ = "http://ns.adobe.com/xap/1.0/\0" + xmpPacket_; } ioDevice->write(xmpPacket_.c_str(), xmpPacket_.length()); return true; } bool parseTagName(const QString &tagString, QString &structName, int &arrayIndex, QString &tagName, const KisMetaData::TypeInfo** typeInfo, const KisMetaData::Schema *schema) { arrayIndex = -1; *typeInfo = 0; int numSubNames = tagString.count('/') + 1; if (numSubNames == 1) { structName.clear(); tagName = tagString; *typeInfo = schema->propertyType(tagName); return true; } if (numSubNames == 2) { QRegExp regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)"); if (regexp.indexIn(tagString) != -1) { structName = regexp.capturedTexts()[1]; tagName = regexp.capturedTexts()[3]; *typeInfo = schema->propertyType(structName); if (*typeInfo && (*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) { *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName); } return true; } QRegExp regexp2("([A-Za-z]\\w+)\\[(\\d+)\\]/([A-Za-z]\\w+):([A-Za-z]\\w+)"); if (regexp2.indexIn(tagString) != -1) { structName = regexp2.capturedTexts()[1]; arrayIndex = regexp2.capturedTexts()[2].toInt() - 1; tagName = regexp2.capturedTexts()[4]; if (schema->propertyType(structName)) { *typeInfo = schema->propertyType(structName)->embeddedPropertyType(); Q_ASSERT(*typeInfo); if ((*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) { *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName); } } return true; } } warnKrita << "WARNING: Unsupported tag. We do not yet support nested tags. The tag will be dropped!"; warnKrita << " Failing tag:" << tagString; return false; } bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const { ioDevice->open(QIODevice::ReadOnly); dbgMetaData << "Load XMP Data"; std::string xmpPacket_; QByteArray arr = ioDevice->readAll(); xmpPacket_.assign(arr.data(), arr.length()); dbgMetaData << xmpPacket_.length(); // dbgMetaData << xmpPacket_.c_str(); Exiv2::XmpData xmpData_; Exiv2::XmpParser::decode(xmpData_, xmpPacket_); QMap< const KisMetaData::Schema*, QMap > > structures; QMap< const KisMetaData::Schema*, QMap > > > arraysOfStructures; for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) { dbgMetaData << "Start iteration" << it->key().c_str(); Exiv2::XmpKey key(it->key()); dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " " << key.ns().c_str(); if ((key.groupName() == "exif" || key.groupName() == "tiff") && key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in adding support for NativeDigest, it's undocumented use by the XMP SDK to check if exif data has been changed while XMP hasn't been updated dbgMetaData << "dropped"; } else { const KisMetaData::Schema* schema = KisMetaData::SchemaRegistry::instance()->schemaFromPrefix(key.groupName().c_str()); if (!schema) { schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str()); if (!schema) { schema = KisMetaData::SchemaRegistry::instance()->create(key.ns().c_str(), key.groupName().c_str()); Q_ASSERT(schema); } } const Exiv2::Value::AutoPtr value = it->getValue(); QString structName; int arrayIndex = -1; QString tagName; const KisMetaData::TypeInfo* typeInfo = 0; if (!parseTagName(key.tagName().c_str(), structName, arrayIndex, tagName, &typeInfo, schema)) continue; bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1; bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != -1; Q_ASSERT(isStructureEntry != isStructureInArrayEntry || !isStructureEntry); KisMetaData::Value v; bool ignoreValue = false; // Compute the value if (value->typeId() == Exiv2::xmpBag || value->typeId() == Exiv2::xmpSeq || value->typeId() == Exiv2::xmpAlt) { const KisMetaData::TypeInfo* embeddedTypeInfo = 0; if (typeInfo) { embeddedTypeInfo = typeInfo->embeddedPropertyType(); } const KisMetaData::Parser* parser = 0; if (embeddedTypeInfo) { parser = embeddedTypeInfo->parser(); } const Exiv2::XmpArrayValue* xav = dynamic_cast(value.get()); Q_ASSERT(xav); QList array; for (int i = 0; i < xav->size(); ++i) { QString value = QString::fromStdString(xav->toString(i)); if (parser) { array.push_back(parser->parse(value)); } else { dbgImage << "No parser " << tagName; array.push_back(KisMetaData::Value(value)); } } KisMetaData::Value::ValueType vt = KisMetaData::Value::Invalid; switch (xav->xmpArrayType()) { case Exiv2::XmpValue::xaNone: warnKrita << "KisXMPIO: Unsupported array"; break; case Exiv2::XmpValue::xaAlt: vt = KisMetaData::Value::AlternativeArray; break; case Exiv2::XmpValue::xaBag: vt = KisMetaData::Value::UnorderedArray; break; case Exiv2::XmpValue::xaSeq: vt = KisMetaData::Value::OrderedArray; break; } v = KisMetaData::Value(array, vt); } else if (value->typeId() == Exiv2::langAlt) { const Exiv2::LangAltValue* xav = dynamic_cast(value.get()); QList alt; for (std::map< std::string, std::string>::const_iterator it = xav->value_.begin(); it != xav->value_.end(); ++it) { KisMetaData::Value valt(it->second.c_str()); valt.addPropertyQualifier("xml:lang", KisMetaData::Value(it->first.c_str())); alt.push_back(valt); } v = KisMetaData::Value(alt, KisMetaData::Value::LangArray); } else { QString valTxt = value->toString().c_str(); if (typeInfo && typeInfo->parser()) { v = typeInfo->parser()->parse(valTxt); } else { dbgMetaData << "No parser " << tagName; v = KisMetaData::Value(valTxt); } if (valTxt == "type=\"Struct\"") { if (!typeInfo || typeInfo->propertyType() == KisMetaData::TypeInfo::StructureType) { ignoreValue = true; } } } // set the value if (isStructureEntry) { structures[schema][structName][tagName] = v; } else if (isStructureInArrayEntry) { if (arraysOfStructures[schema][structName].size() <= arrayIndex) { arraysOfStructures[schema][structName].resize(arrayIndex + 1); } if (!arraysOfStructures[schema][structName][arrayIndex].contains(tagName)) { arraysOfStructures[schema][structName][arrayIndex][tagName] = v; } else { warnKrita << "WARNING: trying to overwrite tag" << tagName << "in" << structName << arrayIndex; } } else { if (!ignoreValue) { store->addEntry(KisMetaData::Entry(schema, tagName, v)); } else { dbgMetaData << "Ignoring value for " << tagName << " " << v; } } } } for (QMap< const KisMetaData::Schema*, QMap > >::iterator it = structures.begin(); it != structures.end(); ++it) { const KisMetaData::Schema* schema = it.key(); for (QMap >::iterator it2 = it.value().begin(); it2 != it.value().end(); ++it2) { store->addEntry(KisMetaData::Entry(schema, it2.key(), KisMetaData::Value(it2.value()))); } } for (QMap< const KisMetaData::Schema*, QMap > > >::iterator it = arraysOfStructures.begin(); it != arraysOfStructures.end(); ++it) { const KisMetaData::Schema* schema = it.key(); for (QMap > >::iterator it2 = it.value().begin(); it2 != it.value().end(); ++it2) { KisMetaData::Value::ValueType type = KisMetaData::Value::OrderedArray; QString entryName = it2.key(); if (schema->propertyType(entryName)) { switch (schema->propertyType(entryName)->propertyType()) { case KisMetaData::TypeInfo::OrderedArrayType: type = KisMetaData::Value::OrderedArray; break; case KisMetaData::TypeInfo::UnorderedArrayType: type = KisMetaData::Value::OrderedArray; break; case KisMetaData::TypeInfo::AlternativeArrayType: type = KisMetaData::Value::AlternativeArray; break; default: type = KisMetaData::Value::Invalid; break; } } else if (store->containsEntry(schema, entryName)) { KisMetaData::Value value = store->getEntry(schema, entryName).value(); if (value.isArray()) { type = value.type(); } } store->removeEntry(schema, entryName); if (type != KisMetaData::Value::Invalid) { QList< KisMetaData::Value > valueList; for (int i = 0; i < it2.value().size(); ++i) { valueList.append(it2.value()[i]); } store->addEntry(KisMetaData::Entry(schema, entryName, KisMetaData::Value(valueList, type))); } } } return true; }