Warning, file /office/calligra/libs/main/KoFilterManager.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> 0003 2000, 2001 Werner Trobin <trobin@kde.org> 0004 Copyright (C) 2004 Nicolas Goutte <goutte@kde.org> 0005 Copyright (C) 2009 Thomas Zander <zander@kde.org> 0006 0007 This library is free software; you can redistribute it and/or 0008 modify it under the terms of the GNU Library General Public 0009 License as published by the Free Software Foundation; either 0010 version 2 of the License, or (at your option) any later version. 0011 0012 This library is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 Library General Public License for more details. 0016 0017 You should have received a copy of the GNU Library General Public License 0018 along with this library; see the file COPYING.LIB. If not, write to 0019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "KoFilterManager.h" 0024 #include "KoFilterManager_p.h" 0025 #include "KoDocument.h" 0026 #include "KoDocumentEntry.h" 0027 #include "KoProgressUpdater.h" 0028 #include <KoConfig.h> // CALLIGRA_OLD_PLUGIN_METADATA 0029 0030 #include <QFile> 0031 #include <QLabel> 0032 #include <QVBoxLayout> 0033 #include <QList> 0034 #include <QApplication> 0035 #include <QByteArray> 0036 #include <QMimeDatabase> 0037 #include <QPluginLoader> 0038 0039 #include <klocalizedstring.h> 0040 #include <kmessagebox.h> 0041 #include <MainDebug.h> 0042 0043 #include <queue> 0044 0045 #include <unistd.h> 0046 0047 // static cache for filter availability 0048 QMap<QString, bool> KoFilterManager::m_filterAvailable; 0049 0050 KoFilterManager::KoFilterManager(KoDocument* document, 0051 KoProgressUpdater* progressUpdater) : 0052 m_document(document), m_parentChain(0), m_graph(""), 0053 d(new Private(progressUpdater)) 0054 { 0055 d->batch = false; 0056 } 0057 0058 0059 KoFilterManager::KoFilterManager(const QString& url, const QByteArray& mimetypeHint, 0060 KoFilterChain* const parentChain) : 0061 m_document(0), m_parentChain(parentChain), m_importUrl(url), m_importUrlMimetypeHint(mimetypeHint), 0062 m_graph(""), d(new Private) 0063 { 0064 d->batch = false; 0065 } 0066 0067 KoFilterManager::KoFilterManager(const QByteArray& mimeType) : 0068 m_document(0), m_parentChain(0), m_graph(""), d(new Private) 0069 { 0070 d->batch = false; 0071 d->importMimeType = mimeType; 0072 } 0073 0074 KoFilterManager::~KoFilterManager() 0075 { 0076 delete d; 0077 } 0078 0079 QString KoFilterManager::importDocument(const QString& url, 0080 const QString& documentMimeType, 0081 KoFilter::ConversionStatus& status) 0082 { 0083 // Find the mime type for the file to be imported. 0084 QString typeName(documentMimeType); 0085 QUrl u(url); 0086 if (documentMimeType.isEmpty()) { 0087 QMimeType t = QMimeDatabase().mimeTypeForUrl(u); 0088 if (t.isValid()) { 0089 typeName = t.name(); 0090 } 0091 } 0092 m_graph.setSourceMimeType(typeName.toLatin1()); // .latin1() is okay here (Werner) 0093 0094 if (!m_graph.isValid()) { 0095 bool userCancelled = false; 0096 0097 warnFilter << "Can't open " << typeName << ", trying filter chooser"; 0098 if (m_document) { 0099 if (!m_document->isAutoErrorHandlingEnabled()) { 0100 status = KoFilter::BadConversionGraph; 0101 return QString(); 0102 } 0103 QByteArray nativeFormat = m_document->nativeFormatMimeType(); 0104 0105 QApplication::setOverrideCursor(Qt::ArrowCursor); 0106 KoFilterChooser chooser(0, 0107 KoFilterManager::mimeFilter(nativeFormat, KoFilterManager::Import, 0108 m_document->extraNativeMimeTypes()), nativeFormat, u); 0109 if (chooser.exec()) { 0110 QByteArray f = chooser.filterSelected().toLatin1(); 0111 if (f == nativeFormat) { 0112 status = KoFilter::OK; 0113 QApplication::restoreOverrideCursor(); 0114 return url; 0115 } 0116 0117 m_graph.setSourceMimeType(f); 0118 } else 0119 userCancelled = true; 0120 QApplication::restoreOverrideCursor(); 0121 } 0122 0123 if (!m_graph.isValid()) { 0124 errorFilter << "Couldn't create a valid graph for this source mimetype: " 0125 << typeName; 0126 importErrorHelper(typeName, userCancelled); 0127 status = KoFilter::BadConversionGraph; 0128 return QString(); 0129 } 0130 } 0131 0132 KoFilterChain::Ptr chain(0); 0133 // Are we owned by a KoDocument? 0134 if (m_document) { 0135 QByteArray mimeType = m_document->nativeFormatMimeType(); 0136 QStringList extraMimes = m_document->extraNativeMimeTypes(); 0137 int i = 0; 0138 int n = extraMimes.count(); 0139 chain = m_graph.chain(this, mimeType); 0140 while (i < n) { 0141 QByteArray extraMime = extraMimes[i].toUtf8(); 0142 // TODO check if its the same target mime then continue 0143 KoFilterChain::Ptr newChain(0); 0144 newChain = m_graph.chain(this, extraMime); 0145 if (!chain || (newChain && newChain->weight() < chain->weight())) 0146 chain = newChain; 0147 ++i; 0148 } 0149 } else if (!d->importMimeType.isEmpty()) { 0150 chain = m_graph.chain(this, d->importMimeType); 0151 } else { 0152 errorFilter << "You aren't supposed to use import() from a filter!" << endl; 0153 status = KoFilter::UsageError; 0154 return QString(); 0155 } 0156 0157 if (!chain) { 0158 errorFilter << "Couldn't create a valid filter chain!" << endl; 0159 importErrorHelper(typeName); 0160 status = KoFilter::BadConversionGraph; 0161 return QString(); 0162 } 0163 0164 // Okay, let's invoke the filters one after the other 0165 m_direction = Import; // vital information! 0166 m_importUrl = url; // We want to load that file 0167 m_exportUrl.clear(); // This is null for sure, as embedded stuff isn't 0168 // allowed to use that method 0169 status = chain->invokeChain(); 0170 0171 m_importUrl.clear(); // Reset the import URL 0172 0173 if (status == KoFilter::OK) 0174 return chain->chainOutput(); 0175 return QString(); 0176 } 0177 0178 KoFilter::ConversionStatus KoFilterManager::exportDocument(const QString& url, QByteArray& mimeType) 0179 { 0180 bool userCancelled = false; 0181 0182 // The import url should already be set correctly (null if we have a KoDocument 0183 // file manager and to the correct URL if we have an embedded manager) 0184 m_direction = Export; // vital information! 0185 m_exportUrl = url; 0186 0187 KoFilterChain::Ptr chain; 0188 if (m_document) { 0189 // We have to pick the right native mimetype as source. 0190 QStringList nativeMimeTypes; 0191 nativeMimeTypes.append(m_document->nativeFormatMimeType()); 0192 nativeMimeTypes += m_document->extraNativeMimeTypes(); 0193 QStringList::ConstIterator it = nativeMimeTypes.constBegin(); 0194 const QStringList::ConstIterator end = nativeMimeTypes.constEnd(); 0195 for (; !chain && it != end; ++it) { 0196 m_graph.setSourceMimeType((*it).toLatin1()); 0197 if (m_graph.isValid()) 0198 chain = m_graph.chain(this, mimeType); 0199 } 0200 } else if (!m_importUrlMimetypeHint.isEmpty()) { 0201 debugFilter << "Using the mimetype hint:" << m_importUrlMimetypeHint; 0202 m_graph.setSourceMimeType(m_importUrlMimetypeHint); 0203 } else { 0204 QUrl u; 0205 u.setPath(m_importUrl); 0206 QMimeType t = QMimeDatabase().mimeTypeForUrl(u); 0207 if (!t.isValid() || t.isDefault()) { 0208 errorFilter << "No mimetype found for" << m_importUrl; 0209 return KoFilter::BadMimeType; 0210 } 0211 m_graph.setSourceMimeType(t.name().toLatin1()); 0212 0213 if (!m_graph.isValid()) { 0214 warnFilter << "Can't open" << t.name() << ", trying filter chooser"; 0215 0216 QApplication::setOverrideCursor(Qt::ArrowCursor); 0217 KoFilterChooser chooser(0, KoFilterManager::mimeFilter(), QString(), u); 0218 if (chooser.exec()) 0219 m_graph.setSourceMimeType(chooser.filterSelected().toLatin1()); 0220 else 0221 userCancelled = true; 0222 0223 QApplication::restoreOverrideCursor(); 0224 } 0225 } 0226 0227 if (!m_graph.isValid()) { 0228 errorFilter << "Couldn't create a valid graph for this source mimetype."; 0229 if (!d->batch && !userCancelled) KMessageBox::error(0, i18n("Could not export file."), i18n("Missing Export Filter")); 0230 return KoFilter::BadConversionGraph; 0231 } 0232 0233 if (!chain) // already set when coming from the m_document case 0234 chain = m_graph.chain(this, mimeType); 0235 0236 if (!chain) { 0237 errorFilter << "Couldn't create a valid filter chain to " << mimeType << " !" << endl; 0238 if (!d->batch) KMessageBox::error(0, i18n("Could not export file."), i18n("Missing Export Filter")); 0239 return KoFilter::BadConversionGraph; 0240 } 0241 0242 return chain->invokeChain(); 0243 } 0244 0245 namespace // in order not to mess with the global namespace ;) 0246 { 0247 // This class is needed only for the static mimeFilter method 0248 class Vertex 0249 { 0250 public: 0251 Vertex(const QByteArray& mimeType) : m_color(White), m_mimeType(mimeType) {} 0252 0253 enum Color { White, Gray, Black }; 0254 Color color() const { 0255 return m_color; 0256 } 0257 void setColor(Color color) { 0258 m_color = color; 0259 } 0260 0261 QByteArray mimeType() const { 0262 return m_mimeType; 0263 } 0264 0265 void addEdge(Vertex* vertex) { 0266 if (vertex) m_edges.append(vertex); 0267 } 0268 QList<Vertex*> edges() const { 0269 return m_edges; 0270 } 0271 0272 private: 0273 Color m_color; 0274 QByteArray m_mimeType; 0275 QList<Vertex*> m_edges; 0276 }; 0277 0278 // Some helper methods for the static stuff 0279 // This method builds up the graph in the passed ascii dict 0280 void buildGraph(QHash<QByteArray, Vertex*>& vertices, KoFilterManager::Direction direction) 0281 { 0282 QStringList stopList; // Lists of mimetypes that are considered end of chains 0283 stopList << "text/plain"; 0284 stopList << "text/csv"; 0285 stopList << "text/x-tex"; 0286 stopList << "text/html"; 0287 0288 // partly copied from build graph, but I don't see any other 0289 // way without crude hacks, as we have to obey the direction here 0290 QList<KoDocumentEntry> parts(KoDocumentEntry::query(QString())); 0291 QList<KoDocumentEntry>::ConstIterator partIt(parts.constBegin()); 0292 QList<KoDocumentEntry>::ConstIterator partEnd(parts.constEnd()); 0293 0294 while (partIt != partEnd) { 0295 QJsonObject metaData = (*partIt).metaData(); 0296 #ifdef CALLIGRA_OLD_PLUGIN_METADATA 0297 QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(','); 0298 #else 0299 QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList(); 0300 #endif 0301 nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString(); 0302 QStringList::ConstIterator it = nativeMimeTypes.constBegin(); 0303 const QStringList::ConstIterator end = nativeMimeTypes.constEnd(); 0304 for (; it != end; ++it) 0305 if (!(*it).isEmpty()) { 0306 const QByteArray key = (*it).toLatin1(); 0307 if (!vertices.contains(key)) 0308 vertices.insert(key, new Vertex(key)); 0309 } 0310 ++partIt; 0311 } 0312 0313 QList<KoFilterEntry::Ptr> filters = KoFilterEntry::query(); // no constraint here - we want *all* :) 0314 QList<KoFilterEntry::Ptr>::ConstIterator it = filters.constBegin(); 0315 QList<KoFilterEntry::Ptr>::ConstIterator end = filters.constEnd(); 0316 foreach(KoFilterEntry::Ptr filterEntry, filters) 0317 for (; it != end; ++it) { 0318 QStringList impList; // Import list 0319 QStringList expList; // Export list 0320 0321 // Now we have to exclude the "stop" mimetypes (in the right direction!) 0322 if (direction == KoFilterManager::Import) { 0323 // Import: "stop" mime type should not appear in export 0324 foreach(const QString & testIt, (*it)->export_) { 0325 if (!stopList.contains(testIt)) 0326 expList.append(testIt); 0327 } 0328 impList = (*it)->import; 0329 } else { 0330 // Export: "stop" mime type should not appear in import 0331 foreach(const QString & testIt, (*it)->import) { 0332 if (!stopList.contains(testIt)) 0333 impList.append(testIt); 0334 } 0335 expList = (*it)->export_; 0336 } 0337 0338 if (impList.empty() || expList.empty()) { 0339 // This filter cannot be used under these conditions 0340 debugFilter << "Filter:" << (*it)->fileName() << " ruled out"; 0341 continue; 0342 } 0343 0344 // First add the "starting points" to the dict 0345 QStringList::ConstIterator importIt = impList.constBegin(); 0346 const QStringList::ConstIterator importEnd = impList.constEnd(); 0347 for (; importIt != importEnd; ++importIt) { 0348 const QByteArray key = (*importIt).toLatin1(); // latin1 is okay here (werner) 0349 // already there? 0350 if (!vertices[ key ]) 0351 vertices.insert(key, new Vertex(key)); 0352 } 0353 0354 // Are we allowed to use this filter at all? 0355 if (KoFilterManager::filterAvailable(*it)) { 0356 QStringList::ConstIterator exportIt = expList.constBegin(); 0357 const QStringList::ConstIterator exportEnd = expList.constEnd(); 0358 for (; exportIt != exportEnd; ++exportIt) { 0359 // First make sure the export vertex is in place 0360 const QByteArray key = (*exportIt).toLatin1(); // latin1 is okay here 0361 Vertex* exp = vertices[ key ]; 0362 if (!exp) { 0363 exp = new Vertex(key); 0364 vertices.insert(key, exp); 0365 } 0366 // Then create the appropriate edges depending on the 0367 // direction (import/export) 0368 // This is the chunk of code which actually differs from the 0369 // graph stuff (apart from the different vertex class) 0370 importIt = impList.constBegin(); // ### TODO: why only the first one? 0371 if (direction == KoFilterManager::Import) { 0372 for (; importIt != importEnd; ++importIt) 0373 exp->addEdge(vertices[(*importIt).toLatin1()]); 0374 } else { 0375 for (; importIt != importEnd; ++importIt) 0376 vertices[(*importIt).toLatin1()]->addEdge(exp); 0377 } 0378 } 0379 } else { 0380 debugFilter << "Filter:" << (*it)->fileName() << " does not apply."; 0381 } 0382 } 0383 } 0384 0385 // This method runs a BFS on the graph to determine the connected 0386 // nodes. Make sure that the graph is "cleared" (the colors of the 0387 // nodes are all white) 0388 QStringList connected(const QHash<QByteArray, Vertex*>& vertices, const QByteArray& mimetype) 0389 { 0390 if (mimetype.isEmpty()) 0391 return QStringList(); 0392 Vertex *v = vertices[ mimetype ]; 0393 if (!v) 0394 return QStringList(); 0395 0396 v->setColor(Vertex::Gray); 0397 std::queue<Vertex*> queue; 0398 queue.push(v); 0399 QStringList connected; 0400 0401 while (!queue.empty()) { 0402 v = queue.front(); 0403 queue.pop(); 0404 QList<Vertex*> edges = v->edges(); 0405 foreach(Vertex* current, edges) { 0406 if (current->color() == Vertex::White) { 0407 current->setColor(Vertex::Gray); 0408 queue.push(current); 0409 } 0410 } 0411 v->setColor(Vertex::Black); 0412 connected.append(v->mimeType()); 0413 } 0414 return connected; 0415 } 0416 } // anon namespace 0417 0418 // The static method to figure out to which parts of the 0419 // graph this mimetype has a connection to. 0420 QStringList KoFilterManager::mimeFilter(const QByteArray &mimetype, Direction direction, const QStringList &extraNativeMimeTypes) 0421 { 0422 //debugFilter <<"mimetype=" << mimetype <<" extraNativeMimeTypes=" << extraNativeMimeTypes; 0423 QHash<QByteArray, Vertex*> vertices; 0424 buildGraph(vertices, direction); 0425 0426 // TODO maybe use the fake vertex trick from the method below, to make the search faster? 0427 0428 QStringList nativeMimeTypes; 0429 nativeMimeTypes.append(QString::fromLatin1(mimetype)); 0430 nativeMimeTypes += extraNativeMimeTypes; 0431 0432 // Add the native mimetypes first so that they are on top. 0433 QStringList lst = nativeMimeTypes; 0434 0435 // Now look for filters which output each of those natives mimetypes 0436 foreach(const QString &natit, nativeMimeTypes) { 0437 const QStringList outMimes = connected(vertices, natit.toLatin1()); 0438 //debugFilter <<"output formats connected to mime" << natit <<" :" << outMimes; 0439 foreach(const QString &mit, outMimes) { 0440 if (!lst.contains(mit)) // append only if not there already. Qt4: QSet<QString>? 0441 lst.append(mit); 0442 } 0443 } 0444 foreach(Vertex* vertex, vertices) { 0445 delete vertex; 0446 } 0447 vertices.clear(); 0448 return lst; 0449 } 0450 0451 QStringList KoFilterManager::mimeFilter() 0452 { 0453 QHash<QByteArray, Vertex*> vertices; 0454 buildGraph(vertices, KoFilterManager::Import); 0455 0456 QList<KoDocumentEntry> parts(KoDocumentEntry::query( QString())); 0457 QList<KoDocumentEntry>::ConstIterator partIt(parts.constBegin()); 0458 QList<KoDocumentEntry>::ConstIterator partEnd(parts.constEnd()); 0459 0460 if (partIt == partEnd) 0461 return QStringList(); 0462 0463 // To find *all* reachable mimetypes, we have to resort to 0464 // a small hat trick, in order to avoid multiple searches: 0465 // We introduce a fake vertex, which is connected to every 0466 // single Calligra mimetype. Due to that one BFS is enough :) 0467 // Now we just need an... ehrm.. unique name for our fake mimetype 0468 Vertex *v = new Vertex("supercalifragilistic/x-pialadocious"); 0469 vertices.insert("supercalifragilistic/x-pialadocious", v); 0470 while (partIt != partEnd) { 0471 QJsonObject metaData = (*partIt).metaData(); 0472 #ifdef CALLIGRA_OLD_PLUGIN_METADATA 0473 QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(','); 0474 #else 0475 QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList(); 0476 #endif 0477 nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString(); 0478 QStringList::ConstIterator it = nativeMimeTypes.constBegin(); 0479 const QStringList::ConstIterator end = nativeMimeTypes.constEnd(); 0480 for (; it != end; ++it) 0481 if (!(*it).isEmpty()) 0482 v->addEdge(vertices[(*it).toLatin1()]); 0483 ++partIt; 0484 } 0485 QStringList result = connected(vertices, "supercalifragilistic/x-pialadocious"); 0486 0487 // Finally we have to get rid of our fake mimetype again 0488 result.removeAll("supercalifragilistic/x-pialadocious"); 0489 return result; 0490 } 0491 0492 // Here we check whether the filter is available. This stuff is quite slow, 0493 // but I don't see any other convenient (for the user) way out :} 0494 bool KoFilterManager::filterAvailable(KoFilterEntry::Ptr entry) 0495 { 0496 if (!entry) 0497 return false; 0498 if (entry->available != "check") 0499 return true; 0500 0501 // QT5TODO 0502 #if 1 0503 return true; 0504 #else 0505 //kDebug( 30500 ) <<"Checking whether" << entry->service()->name() <<" applies."; 0506 // generate some "unique" key 0507 QString key = entry->service()->name() + " - " + entry->service()->library(); 0508 0509 if (!m_filterAvailable.contains(key)) { 0510 //kDebug( 30500 ) <<"Not cached, checking..."; 0511 0512 KLibrary library(QFile::encodeName(entry->service()->library())); 0513 if (library.fileName().isEmpty()) { 0514 warnFilter << "Huh?? Couldn't load the lib: " 0515 << entry->service()->library(); 0516 m_filterAvailable[ key ] = false; 0517 return false; 0518 } 0519 0520 // This code is "borrowed" from klibloader ;) 0521 QByteArray symname = "check_" + QString(library.objectName()).toLatin1(); 0522 KLibrary::void_function_ptr sym = library.resolveFunction(symname); 0523 if (!sym) { 0524 // warnFilter << "The library " << library.objectName() 0525 // << " does not offer a check_" << library.objectName() 0526 // << " function." << endl; 0527 m_filterAvailable[key] = false; 0528 } else { 0529 typedef int (*t_func)(); 0530 t_func check = (t_func)sym; 0531 m_filterAvailable[ key ] = check() == 1; 0532 } 0533 } 0534 return m_filterAvailable[key]; 0535 #endif 0536 } 0537 0538 void KoFilterManager::importErrorHelper(const QString& mimeType, const bool suppressDialog) 0539 { 0540 QString tmp = i18n("Could not import file of type\n%1", mimeType); 0541 // ###### FIXME: use KLibLoader::lastErrorMessage() here 0542 if (!suppressDialog) KMessageBox::error(0, tmp, i18n("Missing Import Filter")); 0543 } 0544 0545 void KoFilterManager::setBatchMode(const bool batch) 0546 { 0547 d->batch = batch; 0548 } 0549 0550 bool KoFilterManager::getBatchMode(void) const 0551 { 0552 return d->batch; 0553 } 0554 0555 KoProgressUpdater* KoFilterManager::progressUpdater() const 0556 { 0557 if (d->progressUpdater.isNull()) { 0558 // somebody, probably its parent, deleted our progress updater for us 0559 return 0; 0560 } 0561 return d->progressUpdater.data(); 0562 }