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(&region));
0418         xmlWriter.endElement();
0419     }
0420     xmlWriter.endElement();
0421 }
0422 
0423 
0424 
0425 
0426 
0427 }  // Sheets
0428 }  // Calligra
0429