diff --git a/sheets/odf/SheetsOdf.h b/sheets/odf/SheetsOdf.h index 236726cf094..8cb9e385834 100644 --- a/sheets/odf/SheetsOdf.h +++ b/sheets/odf/SheetsOdf.h @@ -1,74 +1,72 @@ /* This file is part of the KDE project Copyright 1998-2016 The Calligra Team Copyright 2016 Tomas Mecir Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SHEETS_ODF #define SHEETS_ODF -#include #include -#include "Style.h" #include "sheets_odf_export.h" class QBuffer; class KoOdfReadStore; class KoShapeLoadingContext; +class KoShapeSavingContext; class KoXmlElement; namespace Calligra { namespace Sheets { class Conditions; class DocBase; class Map; -class OdfLoadingContext; -class OdfSavingContext; class ProtectableObject; class Sheet; namespace Odf { bool loadDocument(DocBase *doc, KoOdfReadStore &odfStore); bool saveDocument(DocBase *doc, KoDocument::SavingContext &documentContext); + CALLIGRA_SHEETS_ODF_EXPORT bool loadTableShape(Sheet *sheet, const KoXmlElement &element, KoShapeLoadingContext &context); + CALLIGRA_SHEETS_ODF_EXPORT void saveTableShape(Sheet *sheet, KoShapeSavingContext &context); + void loadProtection(ProtectableObject *prot, const KoXmlElement& element); - CALLIGRA_SHEETS_ODF_EXPORT bool loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash& conditionalStyles); - CALLIGRA_SHEETS_ODF_EXPORT bool saveSheet(Sheet *sheet, OdfSavingContext& tableContext); CALLIGRA_SHEETS_ODF_EXPORT void loadSheetObject(Sheet *sheet, const KoXmlElement& element, KoShapeLoadingContext& shapeContext); CALLIGRA_SHEETS_ODF_EXPORT bool paste(QBuffer &buffer, Map *map); } } // namespace Sheets } // namespace Calligra #endif // SHEETS_ODF diff --git a/sheets/odf/SheetsOdfDoc.cpp b/sheets/odf/SheetsOdfDoc.cpp index a7dc519ea6b..a99c24d855b 100644 --- a/sheets/odf/SheetsOdfDoc.cpp +++ b/sheets/odf/SheetsOdfDoc.cpp @@ -1,331 +1,333 @@ /* This file is part of the KDE project Copyright 1998-2016 The Calligra Team Copyright 2016 Tomas Mecir Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SheetsOdf.h" #include "DocBase.h" #include "BindingModel.h" #include "CalculationSettings.h" #include "Map.h" #include "SheetsDebug.h" #include "SheetAccessModel.h" #include "calligra_sheets_limits.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This file contains functionality to load/save a DocBase namespace Calligra { namespace Sheets { namespace Odf { void loadDocSettings(DocBase *doc, const KoXmlDocument &settingsDoc); void loadDocIgnoreList(DocBase *doc, const KoOasisSettings& settings); void saveSettings(DocBase *doc, KoXmlWriter &settingsWriter); // these are in SheetsOdfMap bool loadMap(Map *map, const KoXmlElement& body, KoOdfLoadingContext& odfContext); void loadMapSettings(Map *map, const KoOasisSettings &settingsDoc); bool saveMap(Map *map, KoXmlWriter & xmlWriter, KoShapeSavingContext & savingContext); // this one is in SheetsOdfSheet void saveSheetSettings(Sheet *sheet, KoXmlWriter &settingsWriter); }; bool Odf::loadDocument(DocBase *doc, KoOdfReadStore &odfStore) { QPointer updater; if (doc->progressUpdater()) { updater = doc->progressUpdater()->startSubtask(1, "Calligra::Sheets::Odf::loadDocument"); updater->setProgress(0); } doc->setSpellListIgnoreAll(QStringList()); KoXmlElement content = odfStore.contentDoc().documentElement(); KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body")); if (realBody.isNull()) { doc->setErrorMessage(i18n("Invalid OASIS OpenDocument file. No office:body tag found.")); doc->map()->deleteLoadingInfo(); return false; } KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, "spreadsheet"); if (body.isNull()) { errorSheetsODF << "No office:spreadsheet found!" << endl; KoXmlElement childElem; QString localName; forEachElement(childElem, realBody) { localName = childElem.localName(); } if (localName.isEmpty()) doc->setErrorMessage(i18n("Invalid OASIS OpenDocument file. No tag found inside office:body.")); else doc->setErrorMessage(i18n("This document is not a spreadsheet, but %1. Please try opening it with the appropriate application." , KoDocument::tagNameToDocumentType(localName))); doc->map()->deleteLoadingInfo(); return false; } // Document Url for FILENAME function and page header/footer. doc->map()->calculationSettings()->setFileName(doc->url().toDisplayString()); KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); // TODO check versions and mimetypes etc. // all goes to workbook if (!loadMap(doc->map(), body, context)) { doc->map()->deleteLoadingInfo(); return false; } if (!odfStore.settingsDoc().isNull()) { loadDocSettings(doc, odfStore.settingsDoc()); } doc->initConfig(); //update plugins that rely on bindings, as loading order can mess up the data of the plugins SheetAccessModel* sheetModel = doc->sheetAccessModel(); QList< Sheet* > sheets = doc->map()->sheetList(); Q_FOREACH( Sheet* sheet, sheets ){ // This region contains the entire sheet const QRect region (0, 0, KS_colMax - 1, KS_rowMax - 1); QModelIndex index = sheetModel->index( 0, doc->map()->indexOf( sheet ) ); QVariant bindingModelValue = sheetModel->data( index , Qt::DisplayRole ); BindingModel *curBindingModel = dynamic_cast< BindingModel* >( qvariant_cast< QPointer< QAbstractItemModel > >( bindingModelValue ).data() ); if ( curBindingModel ){ curBindingModel->emitDataChanged( region ); } } if (updater) updater->setProgress(100); return true; } void Odf::loadDocSettings(DocBase *doc, const KoXmlDocument &settingsDoc) { KoOasisSettings settings(settingsDoc); KoOasisSettings::Items viewSettings = settings.itemSet("view-settings"); if (!viewSettings.isNull()) { doc->setUnit(KoUnit::fromSymbol(viewSettings.parseConfigItemString("unit"))); } loadMapSettings(doc->map(), settings); loadDocIgnoreList(doc, settings); } void Odf::loadDocIgnoreList(DocBase *doc, const KoOasisSettings& settings) { KoOasisSettings::Items configurationSettings = settings.itemSet("configuration-settings"); if (!configurationSettings.isNull()) { const QString ignorelist = configurationSettings.parseConfigItemString("SpellCheckerIgnoreList"); //debugSheets<<" ignorelist :"<setSpellListIgnoreAll (ignorelist.split(',', QString::SkipEmptyParts)); } } bool Odf::saveDocument(DocBase *doc, KoDocument::SavingContext &documentContext) { KoStore * store = documentContext.odfStore.store(); KoXmlWriter * manifestWriter = documentContext.odfStore.manifestWriter(); KoStoreDevice dev(store); KoGenStyles mainStyles;//for compile KoXmlWriter* contentWriter = documentContext.odfStore.contentWriter(); if (!contentWriter) return false; // Document Url for FILENAME function and page header/footer. doc->map()->calculationSettings()->setFileName(doc->url().toDisplayString()); KoXmlWriter* bodyWriter = documentContext.odfStore.bodyWriter(); KoShapeSavingContext savingContext(*bodyWriter, mainStyles, documentContext.embeddedSaver); //todo fixme just add a element for testing saving content.xml bodyWriter->startElement("office:body"); bodyWriter->startElement("office:spreadsheet"); // Saving the map. saveMap(doc->map(), *bodyWriter, savingContext); bodyWriter->endElement(); ////office:spreadsheet bodyWriter->endElement(); ////office:body #warning convert to new odf // Done with writing out the contents to the tempfile, we can now write out the automatic styles mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter); documentContext.odfStore.closeContentWriter(); //add manifest line for content.xml manifestWriter->addManifestEntry("content.xml", "text/xml"); mainStyles.saveOdfStylesDotXml(store, manifestWriter); if (!store->open("settings.xml")) return false; KoXmlWriter* settingsWriter = KoOdfWriteStore::createOasisXmlWriter(&dev, "office:document-settings"); settingsWriter->startElement("office:settings"); settingsWriter->startElement("config:config-item-set"); settingsWriter->addAttribute("config:name", "view-settings"); doc->saveUnitOdf(settingsWriter); saveSettings(doc, *settingsWriter); settingsWriter->endElement(); // config:config-item-set settingsWriter->startElement("config:config-item-set"); settingsWriter->addAttribute("config:name", "configuration-settings"); settingsWriter->addConfigItem("SpellCheckerIgnoreList", doc->spellListIgnoreAll().join(",")); settingsWriter->endElement(); // config:config-item-set settingsWriter->endElement(); // office:settings settingsWriter->endElement(); // Root:element settingsWriter->endDocument(); delete settingsWriter; if (!store->close()) return false; if (!savingContext.saveDataCenter(store, manifestWriter)) { return false; } manifestWriter->addManifestEntry("settings.xml", "text/xml"); doc->setModified(false); return true; } void Odf::saveSettings(DocBase *doc, KoXmlWriter &settingsWriter) { settingsWriter.startElement("config:config-item-map-indexed"); settingsWriter.addAttribute("config:name", "Views"); settingsWriter.startElement("config:config-item-map-entry"); settingsWriter.addConfigItem("ViewId", QString::fromLatin1("View1")); // settingsWriter.startElement("config:config-item-map-named"); settingsWriter.addAttribute("config:name", "Tables"); foreach (Sheet *sheet, doc->map()->sheetList()) { settingsWriter.startElement("config:config-item-map-entry"); settingsWriter.addAttribute("config:name", sheet->sheetName()); saveSheetSettings(sheet, settingsWriter); settingsWriter.endElement(); } settingsWriter.endElement(); settingsWriter.endElement(); settingsWriter.endElement(); } void Odf::loadProtection(ProtectableObject *prot, const KoXmlElement& element) { if (!element.hasAttributeNS(KoXmlNS::table, "protection-key")) return; QString p = element.attributeNS(KoXmlNS::table, "protection-key", QString()); if (p.isNull()) return; QByteArray str(p.toUtf8()); debugSheetsODF <<"Decoding password:" << str; prot->setProtected(KCodecs::base64Decode(str)); } bool Odf::paste(QBuffer &buffer, Map *map) { KoStore * store = KoStore::createStore(&buffer, KoStore::Read); KoOdfReadStore odfStore(store); // does not delete the store on destruction KoXmlDocument doc; QString errorMessage; bool ok = odfStore.loadAndParse("content.xml", doc, errorMessage); if (!ok) { errorSheetsODF << "Error parsing content.xml: " << errorMessage << endl; delete store; return false; } KoOdfStylesReader stylesReader; KoXmlDocument stylesDoc; (void)odfStore.loadAndParse("styles.xml", stylesDoc, errorMessage); // Load styles from style.xml stylesReader.createStyleMap(stylesDoc, true); // Also load styles from content.xml stylesReader.createStyleMap(doc, false); // from KSpreadDoc::loadOdf: KoXmlElement content = doc.documentElement(); KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body")); if (realBody.isNull()) { debugSheetsUI << "Invalid OASIS OpenDocument file. No office:body tag found."; delete store; return false; } KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, "spreadsheet"); if (body.isNull()) { errorSheetsODF << "No office:spreadsheet found!" << endl; delete store; return false; } KoOdfLoadingContext context(stylesReader, store); Q_ASSERT(!stylesReader.officeStyle().isNull()); // all goes to workbook bool result = loadMap(map, body, context); delete store; return result; } + + } // Sheets } // Calligra diff --git a/sheets/odf/SheetsOdfMap.cpp b/sheets/odf/SheetsOdfMap.cpp index ca6171f996f..a563a42d674 100644 --- a/sheets/odf/SheetsOdfMap.cpp +++ b/sheets/odf/SheetsOdfMap.cpp @@ -1,337 +1,391 @@ /* This file is part of the KDE project Copyright 1998-2016 The Calligra Team Copyright 2016 Tomas Mecir Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SheetsOdf.h" #include "CalculationSettings.h" #include "DocBase.h" #include "LoadingInfo.h" #include "Map.h" #include "NamedAreaManager.h" #include "OdfLoadingContext.h" #include "OdfSavingContext.h" #include "RowColumnFormat.h" #include "Sheet.h" #include "StyleManager.h" #include "Validity.h" #include "database/DatabaseManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This file contains functionality to load/save a Map namespace Calligra { namespace Sheets { namespace Odf { bool loadMap(Map *map, const KoXmlElement& body, KoOdfLoadingContext& odfContext); void loadMapSettings(Map *map, const KoOasisSettings &settingsDoc); void fixupStyle(KoCharacterStyle* style); bool saveMap(Map *map, KoXmlWriter & xmlWriter, KoShapeSavingContext & savingContext); // these are in SheetsOdfSheet + bool loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash& conditionalStyles); void loadSheetSettings(Sheet *sheet, const KoOasisSettings::NamedMap &settings); + bool saveSheet(Sheet *sheet, OdfSavingContext& tableContext); } void Odf::fixupStyle(KoCharacterStyle* style) { style->removeHardCodedDefaults(); QTextCharFormat format; style->applyStyle(format); switch (style->underlineStyle()) { case KoCharacterStyle::NoLineStyle: format.setUnderlineStyle(QTextCharFormat::NoUnderline); break; case KoCharacterStyle::SolidLine: format.setUnderlineStyle(QTextCharFormat::SingleUnderline); break; case KoCharacterStyle::DottedLine: format.setUnderlineStyle(QTextCharFormat::DotLine); break; case KoCharacterStyle::DashLine: format.setUnderlineStyle(QTextCharFormat::DashUnderline); break; case KoCharacterStyle::DotDashLine: format.setUnderlineStyle(QTextCharFormat::DashDotLine); break; case KoCharacterStyle::DotDotDashLine: format.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break; case KoCharacterStyle::LongDashLine: format.setUnderlineStyle(QTextCharFormat::DashUnderline); break; case KoCharacterStyle::WaveLine: format.setUnderlineStyle(QTextCharFormat::WaveUnderline); break; } style->copyProperties(format); } bool Odf::loadMap(Map *map, const KoXmlElement& body, KoOdfLoadingContext& odfContext) { map->setLoading(true); map->loadingInfo()->setFileFormat(LoadingInfo::OpenDocument); //load in first #warning TODO new style odf map->styleManager()->loadOdfStyleTemplate(odfContext.stylesReader(), map); OdfLoadingContext tableContext(odfContext); tableContext.validities = Validity::preloadValidities(body); // table:content-validations // load text styles for rich-text content and TOS KoShapeLoadingContext shapeContext(tableContext.odfContext, map->resourceManager()); tableContext.shapeContext = &shapeContext; KoTextSharedLoadingData * sharedData = new KoTextSharedLoadingData(); #warning TODO new style odf sharedData->loadOdfStyles(shapeContext, map->textStyleManager()); fixupStyle((KoCharacterStyle*)map->textStyleManager()->defaultParagraphStyle()); foreach (KoCharacterStyle* style, sharedData->characterStyles(true)) { fixupStyle(style); } foreach (KoCharacterStyle* style, sharedData->characterStyles(false)) { fixupStyle(style); } shapeContext.addSharedData(KOTEXT_SHARED_LOADING_ID, sharedData); QVariant variant; variant.setValue(map->textStyleManager()); map->resourceManager()->setResource(KoText::StyleManager, variant); // load default column style const KoXmlElement* defaultColumnStyle = odfContext.stylesReader().defaultStyle("table-column"); if (defaultColumnStyle) { // debugSheets <<"style:default-style style:family=\"table-column\""; KoStyleStack styleStack; styleStack.push(*defaultColumnStyle); styleStack.setTypeProperties("table-column"); if (styleStack.hasProperty(KoXmlNS::style, "column-width")) { const double width = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "column-width"), -1.0); if (width != -1.0) { // debugSheets <<"\tstyle:column-width:" << width; map->setDefaultColumnWidth(width); } } } // load default row style const KoXmlElement* defaultRowStyle = odfContext.stylesReader().defaultStyle("table-row"); if (defaultRowStyle) { // debugSheets <<"style:default-style style:family=\"table-row\""; KoStyleStack styleStack; styleStack.push(*defaultRowStyle); styleStack.setTypeProperties("table-row"); if (styleStack.hasProperty(KoXmlNS::style, "row-height")) { const double height = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "row-height"), -1.0); if (height != -1.0) { // debugSheets <<"\tstyle:row-height:" << height; map->setDefaultRowHeight(height); } } } #warning use new odf map->calculationSettings()->loadOdf(body); // table::calculation-settings if (body.hasAttributeNS(KoXmlNS::table, "structure-protected")) { loadProtection(map, body); } KoXmlNode sheetNode = KoXml::namedItemNS(body, KoXmlNS::table, "table"); if (sheetNode.isNull()) { // We need at least one sheet ! map->doc()->setErrorMessage(i18n("This document has no sheets (tables).")); map->setLoading(false); return false; } int overallRowCount = 0; while (!sheetNode.isNull()) { KoXmlElement sheetElement = sheetNode.toElement(); if (!sheetElement.isNull()) { //debugSheets<<" Odf::loadMap tableElement is not null"; //debugSheets<<"tableElement.nodeName() :"<addNewSheet(sheetName); sheet->setSheetName(sheetName, true); overallRowCount += KoXml::childNodesCount(sheetElement); } } } // reduce memory usage KoXml::unload(sheetElement); sheetNode = sheetNode.nextSibling(); } map->setOverallRowsCounter(overallRowCount); // used for loading progress info //pre-load auto styles QHash conditionalStyles; #warning TODO new style odf Styles autoStyles = map->styleManager()->loadOdfAutoStyles(odfContext.stylesReader(), conditionalStyles, map->parser()); // load the sheet sheetNode = body.firstChild(); while (!sheetNode.isNull()) { KoXmlElement sheetElement = sheetNode.toElement(); if (!sheetElement.isNull()) { // make it slightly faster KoXml::load(sheetElement); //debugSheets<<"tableElement.nodeName() bis :"<findSheet(name); if (sheet) { #warning use new odf loadSheet(sheet, sheetElement, tableContext, autoStyles, conditionalStyles); } } } } // reduce memory usage KoXml::unload(sheetElement); sheetNode = sheetNode.nextSibling(); } // make sure always at least one sheet exists if (map->count() == 0) { map->addNewSheet(); } //delete any styles which were not used map->styleManager()->releaseUnusedAutoStyles(autoStyles); // Load databases. This needs the sheets to be loaded. #warning TODO new style odf map->databaseManager()->loadOdf(body); // table:database-ranges #warning TODO new style odf map->namedAreaManager()->loadOdf(body); // table:named-expressions map->setLoading(false); return true; } void Odf::loadMapSettings(Map *map, const KoOasisSettings &settings) { KoOasisSettings::Items viewSettings = settings.itemSet("view-settings"); KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap("Views"); KoOasisSettings::Items firstView = viewMap.entry(0); KoOasisSettings::NamedMap sheetsMap = firstView.namedMap("Tables"); debugSheets << " loadMapSettings( KoOasisSettings &settings ) exist :" << !sheetsMap.isNull(); if (!sheetsMap.isNull()) { foreach(Sheet* sheet, map->sheetList()) { loadSheetSettings(sheet, sheetsMap); } } QString activeSheet = firstView.parseConfigItemString("ActiveTable"); debugSheets << " loadMapSettings( KoOasisSettings &settings ) activeSheet :" << activeSheet; if (!activeSheet.isEmpty()) { // Used by View's constructor map->loadingInfo()->setInitialActiveSheet(map->findSheet(activeSheet)); } } bool Odf::saveMap(Map *map, KoXmlWriter & xmlWriter, KoShapeSavingContext & savingContext) { #warning TODO new style odf // Saving the custom cell styles including the default cell style. map->styleManager()->saveOdf(savingContext.mainStyles()); // Saving the default column style KoGenStyle defaultColumnStyle(KoGenStyle::TableColumnStyle, "table-column"); defaultColumnStyle.addPropertyPt("style:column-width", map->defaultColumnFormat()->width()); defaultColumnStyle.setDefaultStyle(true); savingContext.mainStyles().insert(defaultColumnStyle, "Default", KoGenStyles::DontAddNumberToName); // Saving the default row style KoGenStyle defaultRowStyle(KoGenStyle::TableRowStyle, "table-row"); defaultRowStyle.addPropertyPt("style:row-height", map->defaultRowFormat()->height()); defaultRowStyle.setDefaultStyle(true); savingContext.mainStyles().insert(defaultRowStyle, "Default", KoGenStyles::DontAddNumberToName); #warning TODO new style odf map->calculationSettings()->saveOdf(xmlWriter); // table::calculation-settings QByteArray password; map->password(password); if (!password.isNull()) { xmlWriter.addAttribute("table:structure-protected", "true"); QByteArray str = KCodecs::base64Encode(password); // FIXME Stefan: see OpenDocument spec, ch. 17.3 Encryption xmlWriter.addAttribute("table:protection-key", QString(str.data())); } OdfSavingContext tableContext(savingContext); foreach(Sheet* sheet, map->sheetList()) { saveSheet(sheet, tableContext); } tableContext.valStyle.writeStyle(xmlWriter); #warning TODO new style odf map->namedAreaManager()->saveOdf(savingContext.xmlWriter()); #warning TODO new style odf map->databaseManager()->saveOdf(savingContext.xmlWriter()); return true; } +// Table shape is here too, as the code is rather similar to Map load/save + +bool Odf::loadTableShape(Sheet *sheet, const KoXmlElement &element, KoShapeLoadingContext &context) +{ + // pre-load auto styles + KoOdfLoadingContext& odfContext = context.odfLoadingContext(); + OdfLoadingContext tableContext(odfContext); + QHash conditionalStyles; + Map *const map = sheet->map(); + StyleManager *const styleManager = map->styleManager(); + ValueParser *const parser = map->parser(); +#warning use new odf here + Styles autoStyles = styleManager->loadOdfAutoStyles(odfContext.stylesReader(), conditionalStyles, parser); + + if (!element.attributeNS(KoXmlNS::table, "name", QString()).isEmpty()) { + sheet->setSheetName(element.attributeNS(KoXmlNS::table, "name", QString()), true); + } + bool result = loadSheet(sheet, element, tableContext, autoStyles, conditionalStyles); + + // delete any styles which were not used + sheet->map()->styleManager()->releaseUnusedAutoStyles(autoStyles); + + return result; +} + +void Odf::saveTableShape(Sheet *sheet, KoShapeSavingContext &context) +{ + const Map* map = sheet->map(); + // Saving the custom cell styles including the default cell style. +#warning use new odf here + map->styleManager()->saveOdf(context.mainStyles()); + + // Saving the default column style + KoGenStyle defaultColumnStyle(KoGenStyle::TableColumnStyle, "table-column"); + defaultColumnStyle.addPropertyPt("style:column-width", map->defaultColumnFormat()->width()); + defaultColumnStyle.setDefaultStyle(true); + context.mainStyles().insert(defaultColumnStyle, "Default", KoGenStyles::DontAddNumberToName); + + // Saving the default row style + KoGenStyle defaultRowStyle(KoGenStyle::TableRowStyle, "table-row"); + defaultRowStyle.addPropertyPt("style:row-height", map->defaultRowFormat()->height()); + defaultRowStyle.setDefaultStyle(true); + context.mainStyles().insert(defaultRowStyle, "Default", KoGenStyles::DontAddNumberToName); + + OdfSavingContext tableContext(context); + saveSheet(sheet, tableContext); + tableContext.valStyle.writeStyle(context.xmlWriter()); +} + + + + } // Sheets } // Calligra diff --git a/sheets/odf/SheetsOdfSheet.cpp b/sheets/odf/SheetsOdfSheet.cpp index 6d04dac590e..f43758b265d 100644 --- a/sheets/odf/SheetsOdfSheet.cpp +++ b/sheets/odf/SheetsOdfSheet.cpp @@ -1,1786 +1,1788 @@ /* This file is part of the KDE project Copyright 1998-2016 The Calligra Team Copyright 2016 Tomas Mecir Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SheetsOdf.h" #include #include #include #include #include #include #include #include #include #include "KoStore.h" #include #include "KoUnit.h" #include #include #include #include "CellStorage.h" #include "Condition.h" #include "DocBase.h" #include "Formula.h" #include "HeaderFooter.h" #include "LoadingInfo.h" #include "Map.h" #include "OdfLoadingContext.h" #include "OdfSavingContext.h" #include "PrintSettings.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Sheet.h" #include "SheetPrint.h" #include "ShapeApplicationData.h" #include "StyleManager.h" #include "StyleStorage.h" #include "Validity.h" // This file contains functionality to load/save a Sheet namespace Calligra { namespace Sheets { template class IntervalMap { public: IntervalMap() {} // from and to are inclusive, assumes no overlapping ranges // even though no checks are done void insert(int from, int to, const T& data) { m_data.insert(to, qMakePair(from, data)); } T get(int idx) const { typename QMap >::ConstIterator it = m_data.lowerBound(idx); if (it != m_data.end() && it.value().first <= idx) { return it.value().second; } return T(); } private: QMap > m_data; }; namespace Odf { + bool loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash& conditionalStyles); /** * Inserts the styles contained in \p styleRegions into the style storage. * Looks automatic styles up in the map of preloaded automatic styles, * \p autoStyles , and custom styles in the StyleManager. * The region is restricted to \p usedArea . */ void loadSheetInsertStyles(Sheet *sheet, const Styles& autoStyles, const QHash& styleRegions, const QHash& conditionalStyles, const QRect& usedArea, QList >& outStyleRegions, QList >& outConditionalStyles); bool loadStyleFormat(Sheet *sheet, KoXmlElement *style); void loadMasterLayoutPage(Sheet *sheet, KoStyleStack &styleStack); void loadRowNodes(Sheet *sheet, const KoXmlElement& parent, int& rowIndex, int& maxColumn, OdfLoadingContext& tableContext, QHash& rowStyleRegions, QHash& cellStyleRegions, const IntervalMap& columnStyles, const Styles& autoStyles, QList& shapeData); void loadColumnNodes(Sheet *sheet, const KoXmlElement& parent, int& indexCol, int& maxColumn, KoOdfLoadingContext& odfContext, QHash& columnStyleRegions, IntervalMap& columnStyles); bool loadColumnFormat(Sheet *sheet, const KoXmlElement& column, const KoOdfStylesReader& stylesReader, int & indexCol, QHash& columnStyleRegions, IntervalMap& columnStyles); int loadRowFormat(Sheet *sheet, const KoXmlElement& row, int &rowIndex, OdfLoadingContext& tableContext, QHash& rowStyleRegions, QHash& cellStyleRegions, const IntervalMap& columnStyles, const Styles& autoStyles, QList& shapeData); QString getPart(const KoXmlNode & part); void replaceMacro(QString & text, const QString & old, const QString & newS); + bool saveSheet(Sheet *sheet, OdfSavingContext& tableContext); QString saveSheetStyleName(Sheet *sheet, KoGenStyles &mainStyles); void saveColRowCell(Sheet *sheet, int maxCols, int maxRows, OdfSavingContext& tableContext); void saveCells(Sheet *sheet, int row, int maxCols, OdfSavingContext& tableContext); void saveHeaderFooter(Sheet *sheet, KoXmlWriter &xmlWriter); void saveBackgroundImage(Sheet *sheet, KoXmlWriter& xmlWriter); void addText(const QString & text, KoXmlWriter & writer); void convertPart(Sheet *sheet, const QString & part, KoXmlWriter & xmlWriter); bool compareRows(Sheet *sheet, int row1, int row2, int maxCols, OdfSavingContext& tableContext); void loadSheetSettings(Sheet *sheet, const KoOasisSettings::NamedMap &settings); void saveSheetSettings(Sheet *sheet, KoXmlWriter &settingsWriter); } // *************** Loading ***************** bool Odf::loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash& conditionalStyles) { QPointer updater; if (sheet->doc() && sheet->doc()->progressUpdater()) { updater = sheet->doc()->progressUpdater()->startSubtask(1, "Calligra::Sheets::Odf::loadSheet"); updater->setProgress(0); } KoOdfLoadingContext& odfContext = tableContext.odfContext; if (sheetElement.hasAttributeNS(KoXmlNS::table, "style-name")) { QString stylename = sheetElement.attributeNS(KoXmlNS::table, "style-name", QString()); //debugSheetsODF<<" style of table :"<setHidden(!visible); } } if (style->hasAttributeNS(KoXmlNS::style, "master-page-name")) { QString masterPageStyleName = style->attributeNS(KoXmlNS::style, "master-page-name", QString()); //debugSheets<<"style->attribute( style:master-page-name ) :"<hasAttributeNS(KoXmlNS::style, "page-layout-name")) { QString masterPageLayoutStyleName = masterStyle->attributeNS(KoXmlNS::style, "page-layout-name", QString()); //debugSheetsODF<<"masterPageLayoutStyleName :"<hasChildNodes() ) { KoXmlElement element; forEachElement(element, properties) { if (element.nodeName() == "style:background-image") { QString imagePath = element.attributeNS(KoXmlNS::xlink, "href"); KoStore* store = tableContext.odfContext.store(); if (store->hasFile(imagePath)) { QByteArray data; store->extractFile(imagePath, data); QImage image = QImage::fromData(data); if( image.isNull() ) { continue; } sheet->setBackgroundImage(image); Sheet::BackgroundImageProperties bgProperties; if( element.hasAttribute("draw:opacity") ) { QString opacity = element.attribute("draw:opacity", ""); if( opacity.endsWith(QLatin1Char('%')) ) { opacity.chop(1); } bool ok; float opacityFloat = opacity.toFloat( &ok ); if( ok ) { bgProperties.opacity = opacityFloat; } } //TODO //if( element.hasAttribute("style:filterName") ) { //} if( element.hasAttribute("style:position") ) { const QString positionAttribute = element.attribute("style:position",""); const QStringList positionList = positionAttribute.split(' ', QString::SkipEmptyParts); if( positionList.size() == 1) { const QString position = positionList.at(0); if( position == "left" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Left; } if( position == "center" ) { //NOTE the standard is too vague to know what center alone means, we assume that it means both centered bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::HorizontalCenter; bgProperties.verticalPosition = Sheet::BackgroundImageProperties::VerticalCenter; } if( position == "right" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Right; } if( position == "top" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Top; } if( position == "bottom" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Bottom; } } else if (positionList.size() == 2) { const QString verticalPosition = positionList.at(0); const QString horizontalPosition = positionList.at(1); if( horizontalPosition == "left" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Left; } if( horizontalPosition == "center" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::HorizontalCenter; } if( horizontalPosition == "right" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Right; } if( verticalPosition == "top" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Top; } if( verticalPosition == "center" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::VerticalCenter; } if( verticalPosition == "bottom" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Bottom; } } } if( element.hasAttribute("style:repeat") ) { const QString repeat = element.attribute("style:repeat"); if( repeat == "no-repeat" ) { bgProperties.repeat = Sheet::BackgroundImageProperties::NoRepeat; } if( repeat == "repeat" ) { bgProperties.repeat = Sheet::BackgroundImageProperties::Repeat; } if( repeat == "stretch" ) { bgProperties.repeat = Sheet::BackgroundImageProperties::Stretch; } } sheet->setBackgroundImageProperties(bgProperties); } } } } } } // Cell style regions QHash cellStyleRegions; // Cell style regions (row defaults) QHash rowStyleRegions; // Cell style regions (column defaults) QHash columnStyleRegions; IntervalMap columnStyles; // List of shapes that need to have their size recalculated after loading is complete QList shapeData; int rowIndex = 1; int indexCol = 1; int maxColumn = 1; KoXmlNode rowNode = sheetElement.firstChild(); // Some spreadsheet programs may support more rows than // Calligra Sheets so limit the number of repeated rows. // FIXME POSSIBLE DATA LOSS! // First load all style information for rows, columns and cells while (!rowNode.isNull() && rowIndex <= KS_rowMax) { //debugSheetsODF << " rowIndex :" << rowIndex << " indexCol :" << indexCol; KoXmlElement rowElement = rowNode.toElement(); if (!rowElement.isNull()) { // slightly faster KoXml::load(rowElement); //debugSheetsODF << " Odf::loadSheet rowElement.tagName() :" << rowElement.localName(); if (rowElement.namespaceURI() == KoXmlNS::table) { if (rowElement.localName() == "table-header-columns") { // NOTE Handle header cols as ordinary ones // as long as they're not supported. loadColumnNodes(sheet, rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles); } else if (rowElement.localName() == "table-column-group") { loadColumnNodes(sheet, rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles); } else if (rowElement.localName() == "table-column" && indexCol <= KS_colMax) { //debugSheetsODF << " table-column found : index column before" << indexCol; loadColumnFormat(sheet, rowElement, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles); //debugSheetsODF << " table-column found : index column after" << indexCol; maxColumn = qMax(maxColumn, indexCol - 1); } else if (rowElement.localName() == "table-header-rows") { // NOTE Handle header rows as ordinary ones // as long as they're not supported. loadRowNodes(sheet, rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); } else if (rowElement.localName() == "table-row-group") { loadRowNodes(sheet, rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); } else if (rowElement.localName() == "table-row") { //debugSheetsODF << " table-row found :index row before" << rowIndex; int columnMaximal = loadRowFormat(sheet, rowElement, rowIndex, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); // allow the row to define more columns then defined via table-column maxColumn = qMax(maxColumn, columnMaximal); //debugSheetsODF << " table-row found :index row after" << rowIndex; } else if (rowElement.localName() == "shapes") { // OpenDocument v1.1, 8.3.4 Shapes: // The element contains all graphic shapes // with an anchor on the table this element is a child of. KoShapeLoadingContext* shapeLoadingContext = tableContext.shapeContext; KoXmlElement element; forEachElement(element, rowElement) { if (element.namespaceURI() != KoXmlNS::draw) continue; loadSheetObject(sheet, element, *shapeLoadingContext); } } } // don't need it anymore KoXml::unload(rowElement); } rowNode = rowNode.nextSibling(); int count = sheet->map()->increaseLoadedRowsCounter(); if (updater && count >= 0) updater->setProgress(count); } // now recalculate the size for embedded shapes that had sizes specified relative to a bottom-right corner cell foreach (const ShapeLoadingData& sd, shapeData) { // subtract offset because the accumulated width and height we calculate below starts // at the top-left corner of this cell, but the shape can have an offset to that corner QSizeF size = QSizeF( sd.endPoint.x() - sd.offset.x(), sd.endPoint.y() - sd.offset.y()); for (int col = sd.startCell.x(); col < sd.endCell.firstRange().left(); ++col) size += QSizeF(sheet->columnFormat(col)->width(), 0.0); if (sd.endCell.firstRange().top() > sd.startCell.y()) size += QSizeF(0.0, sheet->rowFormats()->totalRowHeight(sd.startCell.y(), sd.endCell.firstRange().top() - 1)); sd.shape->setSize(size); } QList > styleRegions; QList > conditionRegions; // insert the styles into the storage (column defaults) debugSheetsODF << "Inserting column default cell styles ..."; loadSheetInsertStyles(sheet, autoStyles, columnStyleRegions, conditionalStyles, QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions); // insert the styles into the storage (row defaults) debugSheetsODF << "Inserting row default cell styles ..."; loadSheetInsertStyles(sheet, autoStyles, rowStyleRegions, conditionalStyles, QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions); // insert the styles into the storage debugSheetsODF << "Inserting cell styles ..."; loadSheetInsertStyles(sheet, autoStyles, cellStyleRegions, conditionalStyles, QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions); sheet->cellStorage()->loadStyles(styleRegions); sheet->cellStorage()->loadConditions(conditionRegions); if (sheetElement.hasAttributeNS(KoXmlNS::table, "print-ranges")) { // e.g.: Sheet4.A1:Sheet4.E28 QString range = sheetElement.attributeNS(KoXmlNS::table, "print-ranges", QString()); #warning TODO new style odf Region region(Region::loadOdf(range)); if (!region.firstSheet() || sheet->sheetName() == region.firstSheet()->sheetName()) sheet->printSettings()->setPrintRegion(region); } if (sheetElement.attributeNS(KoXmlNS::table, "protected", QString()) == "true") { #warning TODO new style odf loadProtection(sheet, sheetElement); } return true; } void Odf::loadSheetObject(Sheet *sheet, const KoXmlElement& element, KoShapeLoadingContext& shapeContext) { KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext); if (!shape) return; sheet->addShape(shape); dynamic_cast(shape->applicationData())->setAnchoredToCell(false); } void Odf::loadRowNodes(Sheet *sheet, const KoXmlElement& parent, int& rowIndex, int& maxColumn, OdfLoadingContext& tableContext, QHash& rowStyleRegions, QHash& cellStyleRegions, const IntervalMap& columnStyles, const Styles& autoStyles, QList& shapeData ) { KoXmlNode node = parent.firstChild(); while (!node.isNull()) { KoXmlElement elem = node.toElement(); if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) { if (elem.localName() == "table-row") { int columnMaximal = loadRowFormat(sheet, elem, rowIndex, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); // allow the row to define more columns then defined via table-column maxColumn = qMax(maxColumn, columnMaximal); } else if (elem.localName() == "table-row-group") { loadRowNodes(sheet, elem, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); } } node = node.nextSibling(); } } void Odf::loadColumnNodes(Sheet *sheet, const KoXmlElement& parent, int& indexCol, int& maxColumn, KoOdfLoadingContext& odfContext, QHash& columnStyleRegions, IntervalMap& columnStyles ) { KoXmlNode node = parent.firstChild(); while (!node.isNull()) { KoXmlElement elem = node.toElement(); if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) { if (elem.localName() == "table-column") { loadColumnFormat(sheet, elem, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles); maxColumn = qMax(maxColumn, indexCol - 1); } else if (elem.localName() == "table-column-group") { loadColumnNodes(sheet, elem, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles); } } node = node.nextSibling(); } } void Odf::loadSheetInsertStyles(Sheet *sheet, const Styles& autoStyles, const QHash& styleRegions, const QHash& conditionalStyles, const QRect& usedArea, QList >& outStyleRegions, QList >& outConditionalStyles) { const QList styleNames = styleRegions.keys(); for (int i = 0; i < styleNames.count(); ++i) { if (!autoStyles.contains(styleNames[i]) && !sheet->map()->styleManager()->style(styleNames[i])) { warnSheetsODF << "\t" << styleNames[i] << " not used"; continue; } const bool hasConditions = conditionalStyles.contains(styleNames[i]); const QRegion styleRegion = styleRegions[styleNames[i]] & QRegion(usedArea); if (hasConditions) outConditionalStyles.append(qMakePair(styleRegion, conditionalStyles[styleNames[i]])); if (autoStyles.contains(styleNames[i])) { //debugSheetsODF << "\tautomatic:" << styleNames[i] << " at" << styleRegion.rectCount() << "rects"; Style style; style.setDefault(); // "overwrite" existing style style.merge(autoStyles[styleNames[i]]); outStyleRegions.append(qMakePair(styleRegion, style)); } else { const CustomStyle* namedStyle = sheet->map()->styleManager()->style(styleNames[i]); //debugSheetsODF << "\tcustom:" << namedStyle->name() << " at" << styleRegion.rectCount() << "rects"; Style style; style.setDefault(); // "overwrite" existing style style.merge(*namedStyle); outStyleRegions.append(qMakePair(styleRegion, style)); } } } void Odf::replaceMacro(QString & text, const QString & old, const QString & newS) { int n = text.indexOf(old); if (n != -1) text = text.replace(n, old.length(), newS); } QString Odf::getPart(const KoXmlNode & part) { QString result; KoXmlElement e = KoXml::namedItemNS(part, KoXmlNS::text, "p"); while (!e.isNull()) { QString text = e.text(); KoXmlElement macro = KoXml::namedItemNS(e, KoXmlNS::text, "time"); if (!macro.isNull()) replaceMacro(text, macro.text(), "