File indexing completed on 2024-05-12 16:35:39
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 "CalculationSettings.h" 0038 #include "DocBase.h" 0039 #include "LoadingInfo.h" 0040 #include "Map.h" 0041 #include "NamedAreaManager.h" 0042 #include "RowColumnFormat.h" 0043 #include "Sheet.h" 0044 #include "StyleManager.h" 0045 #include "Validity.h" 0046 #include "database/DatabaseManager.h" 0047 0048 #include <KoCharacterStyle.h> 0049 #include <KoDocumentResourceManager.h> 0050 #include <KoGenStyles.h> 0051 #include <KoStyleManager.h> 0052 #include <KoStyleStack.h> 0053 #include <KoText.h> 0054 #include <KoTextSharedLoadingData.h> 0055 #include <KoUnit.h> 0056 #include <KoXmlNS.h> 0057 #include <KoXmlWriter.h> 0058 0059 #include <kcodecs.h> 0060 0061 // This file contains functionality to load/save a Map 0062 0063 namespace Calligra { 0064 namespace Sheets { 0065 0066 namespace Odf { 0067 void fixupStyle(KoCharacterStyle* style); 0068 } 0069 0070 void Odf::fixupStyle(KoCharacterStyle* style) 0071 { 0072 style->removeHardCodedDefaults(); 0073 0074 QTextCharFormat format; 0075 style->applyStyle(format); 0076 switch (style->underlineStyle()) { 0077 case KoCharacterStyle::NoLineStyle: 0078 format.setUnderlineStyle(QTextCharFormat::NoUnderline); break; 0079 case KoCharacterStyle::SolidLine: 0080 format.setUnderlineStyle(QTextCharFormat::SingleUnderline); break; 0081 case KoCharacterStyle::DottedLine: 0082 format.setUnderlineStyle(QTextCharFormat::DotLine); break; 0083 case KoCharacterStyle::DashLine: 0084 format.setUnderlineStyle(QTextCharFormat::DashUnderline); break; 0085 case KoCharacterStyle::DotDashLine: 0086 format.setUnderlineStyle(QTextCharFormat::DashDotLine); break; 0087 case KoCharacterStyle::DotDotDashLine: 0088 format.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break; 0089 case KoCharacterStyle::LongDashLine: 0090 format.setUnderlineStyle(QTextCharFormat::DashUnderline); break; 0091 case KoCharacterStyle::WaveLine: 0092 format.setUnderlineStyle(QTextCharFormat::WaveUnderline); break; 0093 } 0094 style->copyProperties(format); 0095 } 0096 0097 bool Odf::loadMap(Map *map, const KoXmlElement& body, KoOdfLoadingContext& odfContext) 0098 { 0099 map->setLoading(true); 0100 map->loadingInfo()->setFileFormat(LoadingInfo::OpenDocument); 0101 0102 //load in first 0103 loadStyleTemplate(map->styleManager(), odfContext.stylesReader(), map); 0104 0105 OdfLoadingContext tableContext(odfContext); 0106 tableContext.validities = Validity::preloadValidities(body); // table:content-validations 0107 0108 // load text styles for rich-text content and TOS 0109 KoShapeLoadingContext shapeContext(tableContext.odfContext, map->resourceManager()); 0110 tableContext.shapeContext = &shapeContext; 0111 KoTextSharedLoadingData * sharedData = new KoTextSharedLoadingData(); 0112 sharedData->loadOdfStyles(shapeContext, map->textStyleManager()); 0113 0114 fixupStyle((KoCharacterStyle*)map->textStyleManager()->defaultParagraphStyle()); 0115 foreach (KoCharacterStyle* style, sharedData->characterStyles(true)) { 0116 fixupStyle(style); 0117 } 0118 foreach (KoCharacterStyle* style, sharedData->characterStyles(false)) { 0119 fixupStyle(style); 0120 } 0121 shapeContext.addSharedData(KOTEXT_SHARED_LOADING_ID, sharedData); 0122 0123 QVariant variant; 0124 variant.setValue(map->textStyleManager()); 0125 map->resourceManager()->setResource(KoText::StyleManager, variant); 0126 0127 0128 // load default column style 0129 const KoXmlElement* defaultColumnStyle = odfContext.stylesReader().defaultStyle("table-column"); 0130 if (defaultColumnStyle) { 0131 // debugSheets <<"style:default-style style:family=\"table-column\""; 0132 KoStyleStack styleStack; 0133 styleStack.push(*defaultColumnStyle); 0134 styleStack.setTypeProperties("table-column"); 0135 if (styleStack.hasProperty(KoXmlNS::style, "column-width")) { 0136 const double width = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "column-width"), -1.0); 0137 if (width != -1.0) { 0138 // debugSheets <<"\tstyle:column-width:" << width; 0139 map->setDefaultColumnWidth(width); 0140 } 0141 } 0142 } 0143 0144 // load default row style 0145 const KoXmlElement* defaultRowStyle = odfContext.stylesReader().defaultStyle("table-row"); 0146 if (defaultRowStyle) { 0147 // debugSheets <<"style:default-style style:family=\"table-row\""; 0148 KoStyleStack styleStack; 0149 styleStack.push(*defaultRowStyle); 0150 styleStack.setTypeProperties("table-row"); 0151 if (styleStack.hasProperty(KoXmlNS::style, "row-height")) { 0152 const double height = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "row-height"), -1.0); 0153 if (height != -1.0) { 0154 // debugSheets <<"\tstyle:row-height:" << height; 0155 map->setDefaultRowHeight(height); 0156 } 0157 } 0158 } 0159 0160 loadCalculationSettings(map->calculationSettings(), body); // table::calculation-settings 0161 if (body.hasAttributeNS(KoXmlNS::table, "structure-protected")) { 0162 loadProtection(map, body); 0163 } 0164 0165 KoXmlNode sheetNode = KoXml::namedItemNS(body, KoXmlNS::table, "table"); 0166 0167 if (sheetNode.isNull()) { 0168 // We need at least one sheet ! 0169 map->doc()->setErrorMessage(i18n("This document has no sheets (tables).")); 0170 map->setLoading(false); 0171 return false; 0172 } 0173 0174 int overallRowCount = 0; 0175 while (!sheetNode.isNull()) { 0176 KoXmlElement sheetElement = sheetNode.toElement(); 0177 if (!sheetElement.isNull()) { 0178 //debugSheets<<" Odf::loadMap tableElement is not null"; 0179 //debugSheets<<"tableElement.nodeName() :"<<sheetElement.nodeName(); 0180 0181 // make it slightly faster 0182 KoXml::load(sheetElement); 0183 0184 if (sheetElement.nodeName() == "table:table") { 0185 if (!sheetElement.attributeNS(KoXmlNS::table, "name", QString()).isEmpty()) { 0186 const QString sheetName = sheetElement.attributeNS(KoXmlNS::table, "name", QString()); 0187 Sheet* sheet = map->addNewSheet(sheetName); 0188 sheet->setSheetName(sheetName, true); 0189 overallRowCount += KoXml::childNodesCount(sheetElement); 0190 } 0191 } 0192 } 0193 0194 // reduce memory usage 0195 KoXml::unload(sheetElement); 0196 sheetNode = sheetNode.nextSibling(); 0197 } 0198 map->setOverallRowsCounter(overallRowCount); // used for loading progress info 0199 0200 //pre-load auto styles 0201 QHash<QString, Conditions> conditionalStyles; 0202 Styles autoStyles = loadAutoStyles(map->styleManager(), odfContext.stylesReader(), 0203 conditionalStyles, map->parser()); 0204 0205 // load the sheet 0206 sheetNode = body.firstChild(); 0207 while (!sheetNode.isNull()) { 0208 KoXmlElement sheetElement = sheetNode.toElement(); 0209 if (!sheetElement.isNull()) { 0210 // make it slightly faster 0211 KoXml::load(sheetElement); 0212 0213 //debugSheets<<"tableElement.nodeName() bis :"<<sheetElement.nodeName(); 0214 if (sheetElement.nodeName() == "table:table") { 0215 if (!sheetElement.attributeNS(KoXmlNS::table, "name", QString()).isEmpty()) { 0216 QString name = sheetElement.attributeNS(KoXmlNS::table, "name", QString()); 0217 Sheet* sheet = map->findSheet(name); 0218 if (sheet) 0219 loadSheet(sheet, sheetElement, tableContext, autoStyles, conditionalStyles); 0220 } 0221 } 0222 } 0223 0224 // reduce memory usage 0225 KoXml::unload(sheetElement); 0226 sheetNode = sheetNode.nextSibling(); 0227 } 0228 0229 // make sure always at least one sheet exists 0230 if (map->count() == 0) { 0231 map->addNewSheet(); 0232 } 0233 0234 //delete any styles which were not used 0235 map->styleManager()->clearOasisStyles(); 0236 0237 // Load databases. This needs the sheets to be loaded. 0238 ///TODO new style odf 0239 map->databaseManager()->loadOdf(body); // table:database-ranges 0240 loadNamedAreas(map->namedAreaManager(), body); // table:named-expressions 0241 0242 map->setLoading(false); 0243 return true; 0244 } 0245 0246 void Odf::loadMapSettings(Map *map, const KoOasisSettings &settings) 0247 { 0248 KoOasisSettings::Items viewSettings = settings.itemSet("view-settings"); 0249 KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap("Views"); 0250 KoOasisSettings::Items firstView = viewMap.entry(0); 0251 0252 KoOasisSettings::NamedMap sheetsMap = firstView.namedMap("Tables"); 0253 debugSheets << " loadMapSettings( KoOasisSettings &settings ) exist :" << !sheetsMap.isNull(); 0254 if (!sheetsMap.isNull()) { 0255 foreach(Sheet* sheet, map->sheetList()) { 0256 loadSheetSettings(sheet, sheetsMap); 0257 } 0258 } 0259 0260 QString activeSheet = firstView.parseConfigItemString("ActiveTable"); 0261 debugSheets << " loadMapSettings( KoOasisSettings &settings ) activeSheet :" << activeSheet; 0262 0263 if (!activeSheet.isEmpty()) { 0264 // Used by View's constructor 0265 map->loadingInfo()->setInitialActiveSheet(map->findSheet(activeSheet)); 0266 } 0267 } 0268 0269 bool Odf::saveMap(Map *map, KoXmlWriter & xmlWriter, KoShapeSavingContext & savingContext) 0270 { 0271 // Saving the custom cell styles including the default cell style. 0272 saveStyles(map->styleManager(), savingContext.mainStyles()); 0273 0274 // Saving the default column style 0275 KoGenStyle defaultColumnStyle(KoGenStyle::TableColumnStyle, "table-column"); 0276 defaultColumnStyle.addPropertyPt("style:column-width", map->defaultColumnFormat()->width()); 0277 defaultColumnStyle.setDefaultStyle(true); 0278 savingContext.mainStyles().insert(defaultColumnStyle, "Default", KoGenStyles::DontAddNumberToName); 0279 0280 // Saving the default row style 0281 KoGenStyle defaultRowStyle(KoGenStyle::TableRowStyle, "table-row"); 0282 defaultRowStyle.addPropertyPt("style:row-height", map->defaultRowFormat()->height()); 0283 defaultRowStyle.setDefaultStyle(true); 0284 savingContext.mainStyles().insert(defaultRowStyle, "Default", KoGenStyles::DontAddNumberToName); 0285 0286 saveCalculationSettings(map->calculationSettings(), xmlWriter); // table::calculation-settings 0287 0288 QByteArray password; 0289 map->password(password); 0290 if (!password.isNull()) { 0291 xmlWriter.addAttribute("table:structure-protected", "true"); 0292 QByteArray str = KCodecs::base64Encode(password); 0293 // FIXME Stefan: see OpenDocument spec, ch. 17.3 Encryption 0294 xmlWriter.addAttribute("table:protection-key", QString(str.data())); 0295 } 0296 0297 OdfSavingContext tableContext(savingContext); 0298 0299 foreach(Sheet* sheet, map->sheetList()) { 0300 saveSheet(sheet, tableContext); 0301 } 0302 0303 tableContext.valStyle.writeStyle(xmlWriter); 0304 0305 saveNamedAreas(map->namedAreaManager(), savingContext.xmlWriter()); 0306 ///TODO new style odf 0307 map->databaseManager()->saveOdf(savingContext.xmlWriter()); 0308 return true; 0309 } 0310 0311 0312 // Table shape is here too, as the code is rather similar to Map load/save 0313 0314 bool Odf::loadTableShape(Sheet *sheet, const KoXmlElement &element, KoShapeLoadingContext &context) 0315 { 0316 // pre-load auto styles 0317 KoOdfLoadingContext& odfContext = context.odfLoadingContext(); 0318 OdfLoadingContext tableContext(odfContext); 0319 QHash<QString, Conditions> conditionalStyles; 0320 Map *const map = sheet->map(); 0321 StyleManager *const styleManager = map->styleManager(); 0322 ValueParser *const parser = map->parser(); 0323 Styles autoStyles = loadAutoStyles(styleManager, odfContext.stylesReader(), conditionalStyles, parser); 0324 0325 if (!element.attributeNS(KoXmlNS::table, "name", QString()).isEmpty()) { 0326 sheet->setSheetName(element.attributeNS(KoXmlNS::table, "name", QString()), true); 0327 } 0328 bool result = loadSheet(sheet, element, tableContext, autoStyles, conditionalStyles); 0329 0330 // delete any styles which were not used 0331 sheet->map()->styleManager()->clearOasisStyles(); 0332 0333 return result; 0334 } 0335 0336 void Odf::saveTableShape(Sheet *sheet, KoShapeSavingContext &context) 0337 { 0338 const Map* map = sheet->map(); 0339 // Saving the custom cell styles including the default cell style. 0340 saveStyles(map->styleManager(), context.mainStyles()); 0341 0342 // Saving the default column style 0343 KoGenStyle defaultColumnStyle(KoGenStyle::TableColumnStyle, "table-column"); 0344 defaultColumnStyle.addPropertyPt("style:column-width", map->defaultColumnFormat()->width()); 0345 defaultColumnStyle.setDefaultStyle(true); 0346 context.mainStyles().insert(defaultColumnStyle, "Default", KoGenStyles::DontAddNumberToName); 0347 0348 // Saving the default row style 0349 KoGenStyle defaultRowStyle(KoGenStyle::TableRowStyle, "table-row"); 0350 defaultRowStyle.addPropertyPt("style:row-height", map->defaultRowFormat()->height()); 0351 defaultRowStyle.setDefaultStyle(true); 0352 context.mainStyles().insert(defaultRowStyle, "Default", KoGenStyles::DontAddNumberToName); 0353 0354 OdfSavingContext tableContext(context); 0355 saveSheet(sheet, tableContext); 0356 tableContext.valStyle.writeStyle(context.xmlWriter()); 0357 } 0358 0359 void Odf::loadNamedAreas(NamedAreaManager *manager, const KoXmlElement& body) 0360 { 0361 KoXmlNode namedAreas = KoXml::namedItemNS(body, KoXmlNS::table, "named-expressions"); 0362 if (namedAreas.isNull()) return; 0363 0364 debugSheetsODF << "Loading named areas..."; 0365 KoXmlElement element; 0366 forEachElement(element, namedAreas) { 0367 if (element.namespaceURI() != KoXmlNS::table) 0368 continue; 0369 if (element.localName() == "named-range") { 0370 if (!element.hasAttributeNS(KoXmlNS::table, "name")) 0371 continue; 0372 if (!element.hasAttributeNS(KoXmlNS::table, "cell-range-address")) 0373 continue; 0374 0375 // TODO: what is: table:base-cell-address 0376 const QString base = element.attributeNS(KoXmlNS::table, "base-cell-address", QString()); 0377 0378 // Handle the case where the table:base-cell-address does contain the referenced sheetname 0379 // while it's missing in the table:cell-range-address. See bug #194386 for an example. 0380 Sheet* fallbackSheet = 0; 0381 if (!base.isEmpty()) { 0382 Region region(loadRegion(base), manager->map()); 0383 fallbackSheet = region.lastSheet(); 0384 } 0385 0386 const QString name = element.attributeNS(KoXmlNS::table, "name", QString()); 0387 const QString range = element.attributeNS(KoXmlNS::table, "cell-range-address", QString()); 0388 debugSheetsODF << "Named area found, name:" << name << ", area:" << range; 0389 0390 Region region(Odf::loadRegion(range), manager->map(), fallbackSheet); 0391 if (!region.isValid() || !region.lastSheet()) { 0392 debugSheetsODF << "invalid area"; 0393 continue; 0394 } 0395 0396 manager->insert(region, name); 0397 } else if (element.localName() == "named-expression") { 0398 debugSheetsODF << "Named expression found."; 0399 // TODO 0400 } 0401 } 0402 } 0403 0404 void Odf::saveNamedAreas(const NamedAreaManager *manager, KoXmlWriter& xmlWriter) 0405 { 0406 QList<QString> areas = manager->areaNames(); 0407 if (areas.isEmpty()) return; 0408 0409 Region region; 0410 xmlWriter.startElement("table:named-expressions"); 0411 for (int i = 0; i < areas.count(); ++i) { 0412 QString name = areas[i]; 0413 region = manager->namedArea(name); 0414 xmlWriter.startElement("table:named-range"); 0415 xmlWriter.addAttribute("table:name", name); 0416 xmlWriter.addAttribute("table:base-cell-address", Odf::saveRegion(Region(1, 1, manager->sheet(name)).name())); 0417 xmlWriter.addAttribute("table:cell-range-address", Odf::saveRegion(®ion)); 0418 xmlWriter.endElement(); 0419 } 0420 xmlWriter.endElement(); 0421 } 0422 0423 0424 0425 0426 0427 } // Sheets 0428 } // Calligra 0429