File indexing completed on 2024-05-19 16:08:25

0001 /* This file is part of the KDE project
0002    Copyright 1998-2016 The Calligra Team <calligra-devel@kde.org>
0003    Copyright 2016 Tomas Mecir <mecirt@gmail.com>
0004    Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
0005    Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0006    Copyright 2007 Thorsten Zachmann <zachmann@kde.org>
0007    Copyright 2005-2006 Inge Wallin <inge@lysator.liu.se>
0008    Copyright 2004 Ariya Hidayat <ariya@kde.org>
0009    Copyright 2002-2003 Norbert Andres <nandres@web.de>
0010    Copyright 2000-2002 Laurent Montel <montel@kde.org>
0011    Copyright 2002 John Dailey <dailey@vt.edu>
0012    Copyright 2002 Phillip Mueller <philipp.mueller@gmx.de>
0013    Copyright 2000 Werner Trobin <trobin@kde.org>
0014    Copyright 1999-2000 Simon Hausmann <hausmann@kde.org>
0015    Copyright 1999 David Faure <faure@kde.org>
0016    Copyright 1998-2000 Torben Weis <weis@kde.org>
0017 
0018    This library is free software; you can redistribute it and/or
0019    modify it under the terms of the GNU Library General Public
0020    License as published by the Free Software Foundation; either
0021    version 2 of the License, or (at your option) any later version.
0022 
0023    This library is distributed in the hope that it will be useful,
0024    but WITHOUT ANY WARRANTY; without even the implied warranty of
0025    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0026    Library General Public License for more details.
0027 
0028    You should have received a copy of the GNU Library General Public License
0029    along with this library; see the file COPYING.LIB.  If not, write to
0030    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0031    Boston, MA 02110-1301, USA.
0032 */
0033 
0034 #include "SheetsOdf.h"
0035 #include "SheetsOdfPrivate.h"
0036 
0037 #include "DocBase.h"
0038 #include "BindingModel.h"
0039 #include "CalculationSettings.h"
0040 #include "Map.h"
0041 #include "SheetsDebug.h"
0042 #include "SheetAccessModel.h"
0043 #include "calligra_sheets_limits.h"
0044 
0045 #include <KoGenStyles.h>
0046 #include <KoOdfReadStore.h>
0047 #include <KoOdfWriteStore.h>
0048 #include <KoProgressUpdater.h>
0049 #include <KoStore.h>
0050 #include <KoStoreDevice.h>
0051 #include <KoUnit.h>
0052 #include <KoUpdater.h>
0053 #include <KoXmlReader.h>
0054 #include <KoXmlWriter.h>
0055 #include <KoXmlNS.h>
0056 
0057 #include <KCodecs>
0058 #include <QBuffer>
0059 
0060 // This file contains functionality to load/save a DocBase
0061 
0062 namespace Calligra {
0063 namespace Sheets {
0064 
0065 namespace Odf {
0066     void loadDocSettings(DocBase *doc, const KoXmlDocument &settingsDoc);
0067     void loadDocIgnoreList(DocBase *doc, const KoOasisSettings& settings);
0068     void saveSettings(DocBase *doc, KoXmlWriter &settingsWriter);
0069 };
0070 
0071 
0072 bool Odf::loadDocument(DocBase *doc, KoOdfReadStore &odfStore)
0073 {
0074     QPointer<KoUpdater> updater;
0075     if (doc->progressUpdater()) {
0076         updater = doc->progressUpdater()->startSubtask(1, "Calligra::Sheets::Odf::loadDocument");
0077         updater->setProgress(0);
0078     }
0079 
0080     doc->setSpellListIgnoreAll(QStringList());
0081 
0082     KoXmlElement content = odfStore.contentDoc().documentElement();
0083     KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
0084     if (realBody.isNull()) {
0085         doc->setErrorMessage(i18n("Invalid OASIS OpenDocument file. No office:body tag found."));
0086         doc->map()->deleteLoadingInfo();
0087         return false;
0088     }
0089     KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, "spreadsheet");
0090 
0091     if (body.isNull()) {
0092         errorSheetsODF << "No office:spreadsheet found!" << endl;
0093         KoXmlElement childElem;
0094         QString localName;
0095         forEachElement(childElem, realBody) {
0096             localName = childElem.localName();
0097         }
0098         if (localName.isEmpty())
0099             doc->setErrorMessage(i18n("Invalid OASIS OpenDocument file. No tag found inside office:body."));
0100         else
0101             doc->setErrorMessage(i18n("This document is not a spreadsheet, but %1. Please try opening it with the appropriate application." , KoDocument::tagNameToDocumentType(localName)));
0102         doc->map()->deleteLoadingInfo();
0103         return false;
0104     }
0105 
0106     // Document Url for FILENAME function and page header/footer.
0107     doc->map()->calculationSettings()->setFileName(doc->url().toDisplayString());
0108 
0109     KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
0110 
0111     // TODO check versions and mimetypes etc.
0112 
0113     // all <sheet:sheet> goes to workbook
0114     if (!loadMap(doc->map(), body, context)) {
0115         doc->map()->deleteLoadingInfo();
0116         return false;
0117     }
0118 
0119     if (!odfStore.settingsDoc().isNull()) {
0120         loadDocSettings(doc, odfStore.settingsDoc());
0121     }
0122     doc->initConfig();
0123 
0124     //update plugins that rely on bindings, as loading order can mess up the data of the plugins
0125     SheetAccessModel* sheetModel = doc->sheetAccessModel();
0126     QList< Sheet* > sheets = doc->map()->sheetList();
0127     Q_FOREACH( Sheet* sheet, sheets ){
0128         // This region contains the entire sheet
0129         const QRect region (0, 0, KS_colMax - 1, KS_rowMax - 1);
0130         QModelIndex index = sheetModel->index( 0, doc->map()->indexOf( sheet ) );
0131           QVariant bindingModelValue = sheetModel->data( index , Qt::DisplayRole );
0132           BindingModel *curBindingModel = dynamic_cast< BindingModel* >( qvariant_cast< QPointer< QAbstractItemModel > >( bindingModelValue ).data() );
0133           if ( curBindingModel ){
0134               curBindingModel->emitDataChanged( region );
0135           }
0136     }
0137 
0138     if (updater) updater->setProgress(100);
0139 
0140     return true;
0141 }
0142 
0143 void Odf::loadDocSettings(DocBase *doc, const KoXmlDocument &settingsDoc)
0144 {
0145     KoOasisSettings settings(settingsDoc);
0146     KoOasisSettings::Items viewSettings = settings.itemSet("view-settings");
0147     if (!viewSettings.isNull()) {
0148         doc->setUnit(KoUnit::fromSymbol(viewSettings.parseConfigItemString("unit")));
0149     }
0150     loadMapSettings(doc->map(), settings);
0151     loadDocIgnoreList(doc, settings);
0152 }
0153 
0154 void Odf::loadDocIgnoreList(DocBase *doc, const KoOasisSettings& settings)
0155 {
0156     KoOasisSettings::Items configurationSettings = settings.itemSet("configuration-settings");
0157     if (!configurationSettings.isNull()) {
0158         const QString ignorelist = configurationSettings.parseConfigItemString("SpellCheckerIgnoreList");
0159         //debugSheets<<" ignorelist :"<<ignorelist;
0160         doc->setSpellListIgnoreAll (ignorelist.split(',', QString::SkipEmptyParts));
0161     }
0162 }
0163 
0164 bool Odf::saveDocument(DocBase *doc, KoDocument::SavingContext &documentContext)
0165 {
0166     KoStore * store = documentContext.odfStore.store();
0167     KoXmlWriter * manifestWriter = documentContext.odfStore.manifestWriter();
0168 
0169     KoStoreDevice dev(store);
0170     KoGenStyles mainStyles;//for compile
0171 
0172     KoXmlWriter* contentWriter = documentContext.odfStore.contentWriter();
0173     if (!contentWriter) return false;
0174 
0175     // Document Url for FILENAME function and page header/footer.
0176     doc->map()->calculationSettings()->setFileName(doc->url().toDisplayString());
0177 
0178     KoXmlWriter* bodyWriter = documentContext.odfStore.bodyWriter();
0179     KoShapeSavingContext savingContext(*bodyWriter, mainStyles, documentContext.embeddedSaver);
0180 
0181     //todo fixme just add a element for testing saving content.xml
0182     bodyWriter->startElement("office:body");
0183     bodyWriter->startElement("office:spreadsheet");
0184 
0185     // Saving the map.
0186     saveMap(doc->map(), *bodyWriter, savingContext);
0187 
0188     bodyWriter->endElement(); ////office:spreadsheet
0189     bodyWriter->endElement(); ////office:body
0190 
0191     // Done with writing out the contents to the tempfile, we can now write out the automatic styles
0192     mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
0193 
0194     documentContext.odfStore.closeContentWriter();
0195 
0196     //add manifest line for content.xml
0197     manifestWriter->addManifestEntry("content.xml",  "text/xml");
0198 
0199     mainStyles.saveOdfStylesDotXml(store, manifestWriter);
0200 
0201     if (!store->open("settings.xml"))
0202         return false;
0203 
0204     KoXmlWriter* settingsWriter = KoOdfWriteStore::createOasisXmlWriter(&dev, "office:document-settings");
0205     settingsWriter->startElement("office:settings");
0206     settingsWriter->startElement("config:config-item-set");
0207     settingsWriter->addAttribute("config:name", "view-settings");
0208 
0209     doc->saveUnitOdf(settingsWriter);
0210 
0211     saveSettings(doc, *settingsWriter);
0212 
0213     settingsWriter->endElement(); // config:config-item-set
0214 
0215     settingsWriter->startElement("config:config-item-set");
0216     settingsWriter->addAttribute("config:name", "configuration-settings");
0217     settingsWriter->addConfigItem("SpellCheckerIgnoreList", doc->spellListIgnoreAll().join(","));
0218     settingsWriter->endElement(); // config:config-item-set
0219     settingsWriter->endElement(); // office:settings
0220     settingsWriter->endElement(); // Root:element
0221     settingsWriter->endDocument();
0222     delete settingsWriter;
0223 
0224     if (!store->close())
0225         return false;
0226 
0227     if (!savingContext.saveDataCenter(store, manifestWriter)) {
0228         return false;
0229     }
0230 
0231     manifestWriter->addManifestEntry("settings.xml", "text/xml");
0232 
0233     doc->setModified(false);
0234 
0235     return true;
0236 }
0237 
0238 void Odf::saveSettings(DocBase *doc, KoXmlWriter &settingsWriter)
0239 {
0240     settingsWriter.startElement("config:config-item-map-indexed");
0241     settingsWriter.addAttribute("config:name", "Views");
0242     settingsWriter.startElement("config:config-item-map-entry");
0243     settingsWriter.addConfigItem("ViewId", QString::fromLatin1("View1"));
0244     //<config:config-item-map-named config:name="Tables">
0245     settingsWriter.startElement("config:config-item-map-named");
0246     settingsWriter.addAttribute("config:name", "Tables");
0247     foreach (Sheet *sheet, doc->map()->sheetList()) {
0248         settingsWriter.startElement("config:config-item-map-entry");
0249         settingsWriter.addAttribute("config:name", sheet->sheetName());
0250         saveSheetSettings(sheet, settingsWriter);
0251         settingsWriter.endElement();
0252     }
0253     settingsWriter.endElement();
0254     settingsWriter.endElement();
0255     settingsWriter.endElement();
0256 }
0257 
0258 void Odf::loadProtection(ProtectableObject *prot, const KoXmlElement& element)
0259 {
0260     if (!element.hasAttributeNS(KoXmlNS::table, "protection-key")) return;
0261     QString p = element.attributeNS(KoXmlNS::table, "protection-key", QString());
0262     if (p.isNull()) return;
0263 
0264     QByteArray str(p.toUtf8());
0265     debugSheetsODF <<"Decoding password:" << str;
0266     prot->setProtected(KCodecs::base64Decode(str));
0267 }
0268 
0269 bool Odf::paste(QBuffer &buffer, Map *map)
0270 {
0271     KoStore * store = KoStore::createStore(&buffer, KoStore::Read);
0272 
0273     KoOdfReadStore odfStore(store); // does not delete the store on destruction
0274     KoXmlDocument doc;
0275     QString errorMessage;
0276     bool ok = odfStore.loadAndParse("content.xml", doc, errorMessage);
0277     if (!ok) {
0278         errorSheetsODF << "Error parsing content.xml: " << errorMessage << endl;
0279     delete store;
0280         return false;
0281     }
0282 
0283     KoOdfStylesReader stylesReader;
0284     KoXmlDocument stylesDoc;
0285     (void)odfStore.loadAndParse("styles.xml", stylesDoc, errorMessage);
0286     // Load styles from style.xml
0287     stylesReader.createStyleMap(stylesDoc, true);
0288     // Also load styles from content.xml
0289     stylesReader.createStyleMap(doc, false);
0290 
0291     // from KSpreadDoc::loadOdf:
0292     KoXmlElement content = doc.documentElement();
0293     KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
0294     if (realBody.isNull()) {
0295         debugSheetsUI << "Invalid OASIS OpenDocument file. No office:body tag found.";
0296         delete store;
0297         return false;
0298     }
0299     KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, "spreadsheet");
0300 
0301     if (body.isNull()) {
0302         errorSheetsODF << "No office:spreadsheet found!" << endl;
0303         delete store;
0304         return false;
0305     }
0306 
0307     KoOdfLoadingContext context(stylesReader, store);
0308     Q_ASSERT(!stylesReader.officeStyle().isNull());
0309 
0310     // all <sheet:sheet> goes to workbook
0311     bool result = loadMap(map, body, context);
0312 
0313     delete store;
0314 
0315     return result;
0316 }
0317 
0318 void Odf::loadCalculationSettings(CalculationSettings *settings, const KoXmlElement& body)
0319 {
0320     KoXmlNode xmlsettings = KoXml::namedItemNS(body, KoXmlNS::table, "calculation-settings");
0321     debugSheets << "Calculation settings found?" << !xmlsettings.isNull();
0322     if (!xmlsettings.isNull()) {
0323         KoXmlElement element = xmlsettings.toElement();
0324         if (element.hasAttributeNS(KoXmlNS::table,  "case-sensitive")) {
0325             settings->setCaseSensitiveComparisons(Qt::CaseSensitive);
0326             QString value = element.attributeNS(KoXmlNS::table, "case-sensitive", "true");
0327             if (value == "false")
0328                 settings->setCaseSensitiveComparisons(Qt::CaseInsensitive);
0329         } else if (element.hasAttributeNS(KoXmlNS::table, "precision-as-shown")) {
0330             settings->setPrecisionAsShown(false);
0331             QString value = element.attributeNS(KoXmlNS::table, "precision-as-shown", "false");
0332             if (value == "true")
0333                 settings->setPrecisionAsShown(true);
0334         } else if (element.hasAttributeNS(KoXmlNS::table, "search-criteria-must-apply-to-whole-cell")) {
0335             settings->setWholeCellSearchCriteria(true);
0336             QString value = element.attributeNS(KoXmlNS::table, "search-criteria-must-apply-to-whole-cell", "true");
0337             if (value == "false")
0338                 settings->setWholeCellSearchCriteria(false);
0339         } else if (element.hasAttributeNS(KoXmlNS::table, "automatic-find-labels")) {
0340             settings->setAutomaticFindLabels(true);
0341             QString value = element.attributeNS(KoXmlNS::table, "automatic-find-labels", "true");
0342             if (value == "false")
0343                 settings->setAutomaticFindLabels(false);
0344         } else if (element.hasAttributeNS(KoXmlNS::table, "use-regular-expressions")) {
0345             settings->setUseRegularExpressions(true);
0346             QString value = element.attributeNS(KoXmlNS::table, "use-regular-expressions", "true");
0347             if (value == "false")
0348                 settings->setUseRegularExpressions(false);
0349         } else if (element.hasAttributeNS(KoXmlNS::table, "use-wildcards")) {
0350             settings->setUseWildcards(false);
0351             QString value = element.attributeNS(KoXmlNS::table, "use-wildcards", "false");
0352             if (value == "true")
0353                 settings->setUseWildcards(true);
0354         } else if (element.hasAttributeNS(KoXmlNS::table, "null-year")) {
0355             settings->setReferenceYear(1930);
0356             QString value = element.attributeNS(KoXmlNS::table, "null-year", "1930");
0357             if (!value.isEmpty() && value != "1930") {
0358                 bool ok;
0359                 int refYear = value.toInt(&ok);
0360                 if (ok)
0361                     settings->setReferenceYear(refYear);
0362             }
0363         }
0364 
0365         forEachElement(element, xmlsettings) {
0366             if (element.namespaceURI() != KoXmlNS::table)
0367                 continue;
0368             else if (element.tagName() ==  "null-date") {
0369                 settings->setReferenceDate(QDate(1899, 12, 30));
0370                 QString valueType = element.attributeNS(KoXmlNS::table, "value-type", "date");
0371                 if (valueType == "date") {
0372                     QString value = element.attributeNS(KoXmlNS::table, "date-value", "1899-12-30");
0373                     QDate date = QDate::fromString(value, Qt::ISODate);
0374                     if (date.isValid())
0375                         settings->setReferenceDate(date);
0376                 } else {
0377                     debugSheets << "CalculationSettings: Error on loading null date."
0378                     << "Value type """ << valueType << """ not handled"
0379                     << ", falling back to default." << endl;
0380                     // NOTE Stefan: I don't know why different types are possible here!
0381                     // sebsauer: because according to ODF-specs a zero null date can
0382                     // mean QDate::currentDate(). Still unclear what a numeric value !=0
0383                     // means through :-/
0384                 }
0385             } else if (element.tagName() ==  "iteration") {
0386                 // TODO
0387             }
0388         }
0389     }
0390 }
0391 
0392 bool Odf::saveCalculationSettings(const CalculationSettings *settings, KoXmlWriter &xmlWriter)
0393 {
0394     xmlWriter.startElement("table:calculation-settings");
0395     if (!settings->caseSensitiveComparisons())
0396         xmlWriter.addAttribute("table:case-sensitive", "false");
0397     if (settings->precisionAsShown())
0398         xmlWriter.addAttribute("table:precision-as-shown", "true");
0399     if (!settings->wholeCellSearchCriteria())
0400         xmlWriter.addAttribute("table:search-criteria-must-apply-to-whole-cell", "false");
0401     if (!settings->automaticFindLabels())
0402         xmlWriter.addAttribute("table:automatic-find-labels", "false");
0403     if (!settings->useRegularExpressions())
0404         xmlWriter.addAttribute("table:use-regular-expressions", "false");
0405     if (settings->useWildcards())
0406         xmlWriter.addAttribute("table:use-wildcards", "true");
0407     if (settings->referenceYear() != 1930)
0408         xmlWriter.addAttribute("table:null-year", QString::number(settings->referenceYear()));
0409     xmlWriter.endElement();
0410     return true;
0411 }
0412 
0413 
0414 
0415 
0416 }  // Sheets
0417 }  // Calligra