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