Warning, file /office/calligra/libs/odf/KoOdfLoadingContext.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) 2005 David Faure <faure@kde.org>
0003    Copyright (C) 2010 Inge Wallin <inge@lysator.liu.se>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 
0021 // Own
0022 #include "KoOdfLoadingContext.h"
0023 
0024 #include "OdfDebug.h"
0025 
0026 // Calligra
0027 #include <KoOdfReadStore.h>
0028 #include <KoOdfStylesReader.h>
0029 #include <KoStore.h>
0030 #include <KoStoreDevice.h>
0031 #include <KoXmlNS.h>
0032 #include <KoOdfManifestEntry.h>
0033 #include "KoStyleStack.h"
0034 
0035 // Qt
0036 #include <QStandardPaths>
0037 #include <QMimeDatabase>
0038 #include <QMimeType>
0039 
0040 
0041 class Q_DECL_HIDDEN KoOdfLoadingContext::Private
0042 {
0043 public:
0044     Private(KoOdfStylesReader &sr, KoStore *s)
0045         : store(s),
0046         stylesReader(sr),
0047         generatorType(KoOdfLoadingContext::Unknown),
0048         metaXmlParsed(false),
0049         useStylesAutoStyles(false)
0050     {
0051     }
0052 
0053     ~Private() {
0054         qDeleteAll(manifestEntries);
0055     }
0056 
0057     KoStore *store;
0058     KoOdfStylesReader &stylesReader;
0059     KoStyleStack styleStack;
0060 
0061     mutable QString generator;
0062     GeneratorType generatorType;
0063     mutable bool metaXmlParsed;
0064     bool useStylesAutoStyles;
0065 
0066     KoXmlDocument manifestDoc;
0067     QHash<QString, KoOdfManifestEntry *> manifestEntries;
0068 
0069 
0070     KoOdfStylesReader defaultStylesReader;
0071     KoXmlDocument doc; // the doc needs to be kept around so it is possible to access the styles
0072 };
0073 
0074 KoOdfLoadingContext::KoOdfLoadingContext(KoOdfStylesReader &stylesReader, KoStore* store, const QString &defaultStylesResourcePath)
0075         : d(new Private(stylesReader, store))
0076 {
0077     // Ideally this should be done by KoDocument and passed as argument here...
0078     KoOdfReadStore oasisStore(store);
0079     QString dummy;
0080     (void)oasisStore.loadAndParse("tar:/META-INF/manifest.xml", d->manifestDoc, dummy);
0081 
0082     if (!defaultStylesResourcePath.isEmpty()) {
0083         Q_ASSERT(defaultStylesResourcePath.endsWith(QLatin1Char('/')));
0084         const QString fileName =
0085             QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0086                                    defaultStylesResourcePath + "defaultstyles.xml");
0087         if ( ! fileName.isEmpty() ) {
0088             QFile file( fileName );
0089             QString errorMessage;
0090             if ( KoOdfReadStore::loadAndParse( &file, d->doc, errorMessage, fileName ) ) {
0091                 d->defaultStylesReader.createStyleMap( d->doc, true );
0092             }
0093             else {
0094                 warnOdf << "reading of defaultstyles.xml failed:" << errorMessage;
0095             }
0096         }
0097         else {
0098             warnOdf << "defaultstyles.xml not found";
0099         }
0100     }
0101 
0102     if (!parseManifest(d->manifestDoc)) {
0103         debugOdf << "could not parse manifest document";
0104     }
0105 }
0106 
0107 KoOdfLoadingContext::~KoOdfLoadingContext()
0108 {
0109     delete d;
0110 }
0111 
0112 void KoOdfLoadingContext::setManifestFile(const QString& fileName) {
0113     KoOdfReadStore oasisStore(d->store);
0114     QString dummy;
0115     (void)oasisStore.loadAndParse(fileName, d->manifestDoc, dummy);
0116     if (!parseManifest(d->manifestDoc)) {
0117         debugOdf << "could not parse manifest document";
0118     }
0119 }
0120 
0121 void KoOdfLoadingContext::fillStyleStack(const KoXmlElement& object, const QString &nsURI, const QString &attrName, const QString &family)
0122 {
0123     // find all styles associated with an object and push them on the stack
0124     if (object.hasAttributeNS(nsURI, attrName)) {
0125         const QString styleName = object.attributeNS(nsURI, attrName, QString());
0126         const KoXmlElement * style = d->stylesReader.findStyle(styleName, family, d->useStylesAutoStyles);
0127 
0128         if (style)
0129             addStyles(style, family, d->useStylesAutoStyles);
0130         else
0131             warnOdf << "style" << styleName << "not found in" << (d->useStylesAutoStyles ? "styles.xml" : "content.xml");
0132     }
0133 }
0134 
0135 void KoOdfLoadingContext::addStyles(const KoXmlElement* style, const QString &family, bool usingStylesAutoStyles)
0136 {
0137     Q_ASSERT(style);
0138     if (!style) return;
0139 
0140     // this recursive function is necessary as parent styles can have parents themselves
0141     if (style->hasAttributeNS(KoXmlNS::style, "parent-style-name")) {
0142         const QString parentStyleName = style->attributeNS(KoXmlNS::style, "parent-style-name", QString());
0143         const KoXmlElement* parentStyle = d->stylesReader.findStyle(parentStyleName, family, usingStylesAutoStyles);
0144 
0145         if (parentStyle)
0146             addStyles(parentStyle, family, usingStylesAutoStyles);
0147         else {
0148             warnOdf << "Parent style not found: " << family << parentStyleName << usingStylesAutoStyles;
0149             //we are handling a non compliant odf file. let's at the very least load the application default, and the eventual odf default
0150             if (!family.isEmpty()) {
0151                 const KoXmlElement* def = d->stylesReader.defaultStyle(family);
0152                 if (def) {   // then, the default style for this family
0153                     d->styleStack.push(*def);
0154                 }
0155             }
0156         }
0157     } else if (!family.isEmpty()) {
0158         const KoXmlElement* def = d->stylesReader.defaultStyle(family);
0159         if (def) {   // then, the default style for this family
0160             d->styleStack.push(*def);
0161         }
0162     }
0163 
0164     //debugOdf <<"pushing style" << style->attributeNS( KoXmlNS::style,"name", QString() );
0165     d->styleStack.push(*style);
0166 }
0167 
0168 void KoOdfLoadingContext::parseGenerator() const
0169 {
0170     // Regardless of whether we cd into the parent directory
0171     // or not to find a meta.xml, restore the directory that
0172     // we were in afterwards.
0173     d->store->pushDirectory();
0174 
0175     // Some embedded documents to not contain their own meta.xml
0176     // Use the parent directory's instead.
0177     if (!d->store->hasFile("meta.xml"))
0178         // Only has an effect if there is a parent directory
0179         d->store->leaveDirectory();
0180 
0181     if (d->store->hasFile("meta.xml")) {
0182         KoXmlDocument metaDoc;
0183         KoOdfReadStore oasisStore(d->store);
0184         QString errorMsg;
0185         if (oasisStore.loadAndParse("meta.xml", metaDoc, errorMsg)) {
0186             KoXmlNode meta   = KoXml::namedItemNS(metaDoc, KoXmlNS::office, "document-meta");
0187             KoXmlNode office = KoXml::namedItemNS(meta, KoXmlNS::office, "meta");
0188             KoXmlElement generator = KoXml::namedItemNS(office, KoXmlNS::meta, "generator");
0189             if (!generator.isNull()) {
0190                 d->generator = generator.text();
0191                 if (d->generator.startsWith(QLatin1String("Calligra"))) {
0192                     d->generatorType = Calligra;
0193                 }
0194                 // NeoOffice is a port of OpenOffice to Mac OS X
0195                 else if (d->generator.startsWith(QLatin1String("OpenOffice.org")) ||
0196                          d->generator.startsWith(QLatin1String("NeoOffice")) ||
0197                          d->generator.startsWith(QLatin1String("LibreOffice")) ||
0198                          d->generator.startsWith(QLatin1String("StarOffice")) ||
0199                          d->generator.startsWith(QLatin1String("Lotus Symphony"))) {
0200                     d->generatorType = OpenOffice;
0201                 }
0202                 else if (d->generator.startsWith(QLatin1String("MicrosoftOffice"))) {
0203                     d->generatorType = MicrosoftOffice;
0204                 }
0205             }
0206         }
0207     }
0208     d->metaXmlParsed = true;
0209 
0210     d->store->popDirectory();
0211 }
0212 
0213 QString KoOdfLoadingContext::generator() const
0214 {
0215     if (!d->metaXmlParsed && d->store) {
0216         parseGenerator();
0217     }
0218     return d->generator;
0219 }
0220 
0221 KoOdfLoadingContext::GeneratorType KoOdfLoadingContext::generatorType() const
0222 {
0223     if (!d->metaXmlParsed && d->store) {
0224         parseGenerator();
0225     }
0226     return d->generatorType;
0227 }
0228 
0229 KoStore *KoOdfLoadingContext::store() const
0230 {
0231     return d->store;
0232 }
0233 
0234 KoOdfStylesReader &KoOdfLoadingContext::stylesReader()
0235 {
0236     return d->stylesReader;
0237 }
0238 
0239 /**
0240 * Get the application default styles styleReader
0241 */
0242 KoOdfStylesReader &KoOdfLoadingContext::defaultStylesReader()
0243 {
0244     return d->defaultStylesReader;
0245 }
0246 
0247 KoStyleStack &KoOdfLoadingContext::styleStack() const
0248 {
0249     return d->styleStack;
0250 }
0251 
0252 void KoOdfLoadingContext::setUseStylesAutoStyles(bool useStylesAutoStyles)
0253 {
0254     d->useStylesAutoStyles = useStylesAutoStyles;
0255 }
0256 
0257 bool KoOdfLoadingContext::useStylesAutoStyles() const
0258 {
0259     return d->useStylesAutoStyles;
0260 }
0261 
0262 QString KoOdfLoadingContext::mimeTypeForPath(const QString& path, bool guess) const
0263 {
0264     QHash<QString, KoOdfManifestEntry *>::ConstIterator it(d->manifestEntries.constFind(path));
0265     if (it == d->manifestEntries.constEnd()) {
0266         // try to find it with an added / at the end
0267         QString dirPath = path + '/';
0268         it = d->manifestEntries.constFind(dirPath);
0269     }
0270     if (it != d->manifestEntries.constEnd()) {
0271         QString mimeType = it.value()->mediaType();
0272 
0273         // figure out mimetype by content if it is not provided
0274         if (mimeType.isEmpty() && guess) {
0275             Q_ASSERT(!d->store->isOpen());
0276             if (d->store->open(path)) {
0277                 KoStoreDevice device(d->store);
0278                 QByteArray data = device.read(16384);
0279                 d->store->close();
0280                 QMimeDatabase db;
0281                 QMimeType mtp = db.mimeTypeForData(data);
0282                 mimeType = mtp.name();
0283                 if (!mimeType.isEmpty()) {
0284                     it.value()->setMediaType(mimeType);
0285                 }
0286             }
0287         }
0288 
0289         return mimeType;
0290     }
0291     else {
0292         return QString();
0293     }
0294 }
0295 
0296 QList<KoOdfManifestEntry*> KoOdfLoadingContext::manifestEntries() const
0297 {
0298     return d->manifestEntries.values();
0299 }
0300 
0301 bool KoOdfLoadingContext::parseManifest(const KoXmlDocument &manifestDocument)
0302 {
0303     // First find the manifest:manifest node.
0304     KoXmlNode  n = manifestDocument.firstChild();
0305     debugOdf << "Searching for manifest:manifest " << n.toElement().nodeName();
0306     for (; !n.isNull(); n = n.nextSibling()) {
0307         if (!n.isElement()) {
0308             debugOdf << "NOT element";
0309             continue;
0310         } else {
0311             debugOdf << "element";
0312         }
0313 
0314         debugOdf << "name:" << n.toElement().localName()
0315                       << "namespace:" << n.toElement().namespaceURI();
0316 
0317         if (n.toElement().localName() == "manifest"
0318             && n.toElement().namespaceURI() == KoXmlNS::manifest)
0319         {
0320             debugOdf << "found manifest:manifest";
0321             break;
0322         }
0323     }
0324     if (n.isNull()) {
0325         debugOdf << "Could not find manifest:manifest";
0326         return false;
0327     }
0328 
0329     // Now loop through the children of the manifest:manifest and
0330     // store all the manifest:file-entry elements.
0331     const KoXmlElement  manifestElement = n.toElement();
0332     for (n = manifestElement.firstChild(); !n.isNull(); n = n.nextSibling()) {
0333 
0334         if (!n.isElement())
0335             continue;
0336 
0337         KoXmlElement el = n.toElement();
0338         if (!(el.localName() == "file-entry" && el.namespaceURI() == KoXmlNS::manifest))
0339             continue;
0340 
0341         QString fullPath  = el.attributeNS(KoXmlNS::manifest, "full-path", QString());
0342         QString mediaType = el.attributeNS(KoXmlNS::manifest, "media-type", QString(""));
0343         QString version   = el.attributeNS(KoXmlNS::manifest, "version", QString());
0344 
0345         // Only if fullPath is valid, should we store this entry.
0346         // If not, we don't bother to find out exactly what is wrong, we just skip it.
0347         if (!fullPath.isNull()) {
0348             d->manifestEntries.insert(fullPath,
0349                                       new KoOdfManifestEntry(fullPath, mediaType, version));
0350         }
0351     }
0352 
0353     return true;
0354 }