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 }