File indexing completed on 2024-05-12 16:35:40

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 <kcodecs.h>
0038 
0039 #include <KoDocumentInfo.h>
0040 #include <KoGenStyles.h>
0041 #include <KoProgressUpdater.h>
0042 #include <KoShape.h>
0043 #include <KoShapeRegistry.h>
0044 #include "KoStore.h"
0045 #include <KoStyleStack.h>
0046 #include "KoUnit.h"
0047 #include <KoUpdater.h>
0048 #include <KoXmlNS.h>
0049 #include <KoXmlWriter.h>
0050 
0051 #include "CellStorage.h"
0052 #include "Condition.h"
0053 #include "DocBase.h"
0054 #include "Formula.h"
0055 #include "HeaderFooter.h"
0056 #include "LoadingInfo.h"
0057 #include "Map.h"
0058 #include "PrintSettings.h"
0059 #include "RowColumnFormat.h"
0060 #include "RowFormatStorage.h"
0061 #include "Sheet.h"
0062 #include "SheetPrint.h"
0063 #include "ShapeApplicationData.h"
0064 #include "StyleManager.h"
0065 #include "StyleStorage.h"
0066 #include "Validity.h"
0067 
0068 // This file contains functionality to load/save a Sheet
0069 
0070 namespace Calligra {
0071 namespace Sheets {
0072 
0073 class Cell;
0074 
0075 template<typename T> class IntervalMap
0076 {
0077 public:
0078     IntervalMap() {}
0079     // from and to are inclusive, assumes no overlapping ranges
0080     // even though no checks are done
0081     void insert(int from, int to, const T& data) {
0082         m_data.insert(to, qMakePair(from, data));
0083     }
0084     T get(int idx) const {
0085         typename QMap<int, QPair<int, T> >::ConstIterator it = m_data.lowerBound(idx);
0086         if (it != m_data.end() && it.value().first <= idx) {
0087             return it.value().second;
0088         }
0089         return T();
0090     }
0091 private:
0092     QMap<int, QPair<int, T> > m_data;
0093 };
0094 
0095 
0096 namespace Odf {
0097     // Sheet loading - helper functions
0098     /**
0099      * Inserts the styles contained in \p styleRegions into the style storage.
0100      * Looks automatic styles up in the map of preloaded automatic styles,
0101      * \p autoStyles , and custom styles in the StyleManager.
0102      * The region is restricted to \p usedArea .
0103      */
0104     void loadSheetInsertStyles(Sheet *sheet, const Styles& autoStyles,
0105                              const QHash<QString, QRegion>& styleRegions,
0106                              const QHash<QString, Conditions>& conditionalStyles,
0107                              const QRect& usedArea,
0108                              QList<QPair<QRegion, Style> >& outStyleRegions,
0109                              QList<QPair<QRegion, Conditions> >& outConditionalStyles);
0110 
0111     bool loadStyleFormat(Sheet *sheet, KoXmlElement *style);
0112     void loadMasterLayoutPage(Sheet *sheet, KoStyleStack &styleStack);
0113     void loadRowNodes(Sheet *sheet, const KoXmlElement& parent,
0114                             int& rowIndex,
0115                             int& maxColumn,
0116                             OdfLoadingContext& tableContext,
0117                             QHash<QString, QRegion>& rowStyleRegions,
0118                             QHash<QString, QRegion>& cellStyleRegions,
0119                             const IntervalMap<QString>& columnStyles,
0120                             const Styles& autoStyles,
0121                             QList<ShapeLoadingData>& shapeData);
0122     void loadColumnNodes(Sheet *sheet, const KoXmlElement& parent,
0123                             int& indexCol,
0124                             int& maxColumn,
0125                             KoOdfLoadingContext& odfContext,
0126                             QHash<QString, QRegion>& columnStyleRegions,
0127                             IntervalMap<QString>& columnStyles);
0128     bool loadColumnFormat(Sheet *sheet, const KoXmlElement& column,
0129                              const KoOdfStylesReader& stylesReader, int & indexCol,
0130                              QHash<QString, QRegion>& columnStyleRegions, IntervalMap<QString>& columnStyles);
0131     int loadRowFormat(Sheet *sheet, const KoXmlElement& row, int &rowIndex,
0132                           OdfLoadingContext& tableContext,
0133                           QHash<QString, QRegion>& rowStyleRegions,
0134                           QHash<QString, QRegion>& cellStyleRegions,
0135                           const IntervalMap<QString>& columnStyles,
0136                           const Styles& autoStyles,
0137                           QList<ShapeLoadingData>& shapeData);
0138     QString getPart(const KoXmlNode & part);
0139     void replaceMacro(QString & text, const QString & old, const QString & newS);
0140 
0141     // Sheet saving - helper functions
0142     QString saveSheetStyleName(Sheet *sheet, KoGenStyles &mainStyles);
0143     void saveColRowCell(Sheet *sheet, int maxCols, int maxRows, OdfSavingContext& tableContext);
0144     void saveCells(Sheet *sheet, int row, int maxCols, OdfSavingContext& tableContext);
0145     void saveHeaderFooter(Sheet *sheet, KoXmlWriter &xmlWriter);
0146     void saveBackgroundImage(Sheet *sheet, KoXmlWriter& xmlWriter);
0147     void addText(const QString & text, KoXmlWriter & writer);
0148     void convertPart(Sheet *sheet, const QString & part, KoXmlWriter & xmlWriter);
0149     bool compareRows(Sheet *sheet, int row1, int row2, int maxCols, OdfSavingContext& tableContext);
0150     QString savePageLayout(PrintSettings *settings, KoGenStyles &mainStyles, bool formulas, bool zeros);
0151 }
0152 
0153 // *************** Loading *****************
0154 
0155 bool Odf::loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash<QString, Conditions>& conditionalStyles)
0156 {
0157     QPointer<KoUpdater> updater;
0158     if (sheet->doc() && sheet->doc()->progressUpdater()) {
0159         updater = sheet->doc()->progressUpdater()->startSubtask(1,
0160                                                      "Calligra::Sheets::Odf::loadSheet");
0161         updater->setProgress(0);
0162     }
0163 
0164     KoOdfLoadingContext& odfContext = tableContext.odfContext;
0165     if (sheetElement.hasAttributeNS(KoXmlNS::table, "style-name")) {
0166         QString stylename = sheetElement.attributeNS(KoXmlNS::table, "style-name", QString());
0167         //debugSheetsODF<<" style of table :"<<stylename;
0168         const KoXmlElement *style = odfContext.stylesReader().findStyle(stylename, "table");
0169         Q_ASSERT(style);
0170         //debugSheetsODF<<" style :"<<style;
0171         if (style) {
0172             KoXmlElement properties(KoXml::namedItemNS(*style, KoXmlNS::style, "table-properties"));
0173             if (!properties.isNull()) {
0174                 if (properties.hasAttributeNS(KoXmlNS::table, "display")) {
0175                     bool visible = (properties.attributeNS(KoXmlNS::table, "display", QString()) == "true" ? true : false);
0176                     sheet->setHidden(!visible);
0177                 }
0178             }
0179             if (style->hasAttributeNS(KoXmlNS::style, "master-page-name")) {
0180                 QString masterPageStyleName = style->attributeNS(KoXmlNS::style, "master-page-name", QString());
0181                 //debugSheets<<"style->attribute( style:master-page-name ) :"<<masterPageStyleName;
0182                 KoXmlElement *masterStyle = odfContext.stylesReader().masterPages()[masterPageStyleName];
0183                 //debugSheets<<"stylesReader.styles()[masterPageStyleName] :"<<masterStyle;
0184                 if (masterStyle) {
0185                     loadStyleFormat(sheet, masterStyle);
0186                     if (masterStyle->hasAttributeNS(KoXmlNS::style, "page-layout-name")) {
0187                         QString masterPageLayoutStyleName = masterStyle->attributeNS(KoXmlNS::style, "page-layout-name", QString());
0188                         //debugSheetsODF<<"masterPageLayoutStyleName :"<<masterPageLayoutStyleName;
0189                         const KoXmlElement *masterLayoutStyle = odfContext.stylesReader().findStyle(masterPageLayoutStyleName);
0190                         if (masterLayoutStyle) {
0191                             //debugSheetsODF<<"masterLayoutStyle :"<<masterLayoutStyle;
0192                             KoStyleStack styleStack;
0193                             styleStack.setTypeProperties("page-layout");
0194                             styleStack.push(*masterLayoutStyle);
0195                             loadMasterLayoutPage(sheet, styleStack);
0196                         }
0197                     }
0198                 }
0199             }
0200 
0201             if (style->hasChildNodes() ) {
0202                 KoXmlElement element;
0203                 forEachElement(element, properties) {
0204                     if (element.nodeName() == "style:background-image") {
0205                         QString imagePath = element.attributeNS(KoXmlNS::xlink, "href");
0206                         KoStore* store = tableContext.odfContext.store();
0207                         if (store->hasFile(imagePath)) {
0208                             QByteArray data;
0209                             store->extractFile(imagePath, data);
0210                             QImage image = QImage::fromData(data);
0211 
0212                             if( image.isNull() ) {
0213                                 continue;
0214                             }
0215 
0216                             sheet->setBackgroundImage(image);
0217 
0218                             Sheet::BackgroundImageProperties bgProperties;
0219                             if( element.hasAttribute("draw:opacity") ) {
0220                                 QString opacity = element.attribute("draw:opacity", "");
0221                                 if( opacity.endsWith(QLatin1Char('%')) ) {
0222                                     opacity.chop(1);
0223                                 }
0224                                 bool ok;
0225                                 float opacityFloat = opacity.toFloat( &ok );
0226                                 if( ok ) {
0227                                     bgProperties.opacity = opacityFloat;
0228                                 }
0229                             }
0230                             //TODO
0231                             //if( element.hasAttribute("style:filterName") ) {
0232                             //}
0233                             if( element.hasAttribute("style:position") ) {
0234                                 const QString positionAttribute = element.attribute("style:position","");
0235                                 const QStringList positionList = positionAttribute.split(' ', QString::SkipEmptyParts);
0236                                 if( positionList.size() == 1) {
0237                                     const QString position = positionList.at(0);
0238                                     if( position == "left" ) {
0239                                         bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Left;
0240                                     }
0241                                     if( position == "center" ) {
0242                                         //NOTE the standard is too vague to know what center alone means, we assume that it means both centered
0243                                         bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::HorizontalCenter;
0244                                         bgProperties.verticalPosition = Sheet::BackgroundImageProperties::VerticalCenter;
0245                                     }
0246                                     if( position == "right" ) {
0247                                         bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Right;
0248                                     }
0249                                     if( position == "top" ) {
0250                                         bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Top;
0251                                     }
0252                                     if( position == "bottom" ) {
0253                                         bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Bottom;
0254                                     }
0255                                 }
0256                                 else if (positionList.size() == 2) {
0257                                     const QString verticalPosition = positionList.at(0);
0258                                     const QString horizontalPosition = positionList.at(1);
0259                                     if( horizontalPosition == "left" ) {
0260                                         bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Left;
0261                                     }
0262                                     if( horizontalPosition == "center" ) {
0263                                         bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::HorizontalCenter;
0264                                     }
0265                                     if( horizontalPosition == "right" ) {
0266                                         bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Right;
0267                                     }
0268                                     if( verticalPosition == "top" ) {
0269                                         bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Top;
0270                                     }
0271                                     if( verticalPosition == "center" ) {
0272                                         bgProperties.verticalPosition = Sheet::BackgroundImageProperties::VerticalCenter;
0273                                     }
0274                                     if( verticalPosition == "bottom" ) {
0275                                         bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Bottom;
0276                                     }
0277                                 }
0278                             }
0279                             if( element.hasAttribute("style:repeat") ) {
0280                                 const QString repeat = element.attribute("style:repeat");
0281                                 if( repeat == "no-repeat" ) {
0282                                     bgProperties.repeat = Sheet::BackgroundImageProperties::NoRepeat;
0283                                 }
0284                                 if( repeat == "repeat" ) {
0285                                     bgProperties.repeat = Sheet::BackgroundImageProperties::Repeat;
0286                                 }
0287                                 if( repeat == "stretch" ) {
0288                                     bgProperties.repeat = Sheet::BackgroundImageProperties::Stretch;
0289                                 }
0290                             }
0291                             sheet->setBackgroundImageProperties(bgProperties);
0292                         }
0293                     }
0294                 }
0295 
0296             }
0297         }
0298     }
0299 
0300     // Cell style regions
0301     QHash<QString, QRegion> cellStyleRegions;
0302     // Cell style regions (row defaults)
0303     QHash<QString, QRegion> rowStyleRegions;
0304     // Cell style regions (column defaults)
0305     QHash<QString, QRegion> columnStyleRegions;
0306     IntervalMap<QString> columnStyles;
0307 
0308     // List of shapes that need to have their size recalculated after loading is complete
0309     QList<ShapeLoadingData> shapeData;
0310 
0311     int rowIndex = 1;
0312     int indexCol = 1;
0313     int maxColumn = 1;
0314     KoXmlNode rowNode = sheetElement.firstChild();
0315     // Some spreadsheet programs may support more rows than
0316     // Calligra Sheets so limit the number of repeated rows.
0317     // FIXME POSSIBLE DATA LOSS!
0318 
0319     // First load all style information for rows, columns and cells
0320     while (!rowNode.isNull() && rowIndex <= KS_rowMax) {
0321         //debugSheetsODF << " rowIndex :" << rowIndex << " indexCol :" << indexCol;
0322         KoXmlElement rowElement = rowNode.toElement();
0323         if (!rowElement.isNull()) {
0324             // slightly faster
0325             KoXml::load(rowElement);
0326 
0327             //debugSheetsODF << " Odf::loadSheet rowElement.tagName() :" << rowElement.localName();
0328             if (rowElement.namespaceURI() == KoXmlNS::table) {
0329                 if (rowElement.localName() == "table-header-columns") {
0330                     // NOTE Handle header cols as ordinary ones
0331                     //      as long as they're not supported.
0332                     loadColumnNodes(sheet, rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles);
0333                 } else if (rowElement.localName() == "table-column-group") {
0334                     loadColumnNodes(sheet, rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles);
0335                 } else if (rowElement.localName() == "table-column" && indexCol <= KS_colMax) {
0336                     //debugSheetsODF << " table-column found : index column before" << indexCol;
0337                     loadColumnFormat(sheet, rowElement, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles);
0338                     //debugSheetsODF << " table-column found : index column after" << indexCol;
0339                     maxColumn = qMax(maxColumn, indexCol - 1);
0340                 } else if (rowElement.localName() == "table-header-rows") {
0341                     // NOTE Handle header rows as ordinary ones
0342                     //      as long as they're not supported.
0343                     loadRowNodes(sheet, rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
0344                 } else if (rowElement.localName() == "table-row-group") {
0345                     loadRowNodes(sheet, rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
0346                 } else if (rowElement.localName() == "table-row") {
0347                     //debugSheetsODF << " table-row found :index row before" << rowIndex;
0348                     int columnMaximal = loadRowFormat(sheet, rowElement, rowIndex, tableContext,
0349                                   rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
0350                     // allow the row to define more columns then defined via table-column
0351                     maxColumn = qMax(maxColumn, columnMaximal);
0352                     //debugSheetsODF << " table-row found :index row after" << rowIndex;
0353                 } else if (rowElement.localName() == "shapes") {
0354                     // OpenDocument v1.1, 8.3.4 Shapes:
0355                     // The <table:shapes> element contains all graphic shapes
0356                     // with an anchor on the table this element is a child of.
0357                     KoShapeLoadingContext* shapeLoadingContext = tableContext.shapeContext;
0358                     KoXmlElement element;
0359                     forEachElement(element, rowElement) {
0360                         if (element.namespaceURI() != KoXmlNS::draw)
0361                             continue;
0362                         loadSheetObject(sheet, element, *shapeLoadingContext);
0363                     }
0364                 }
0365             }
0366 
0367             // don't need it anymore
0368             KoXml::unload(rowElement);
0369         }
0370 
0371         rowNode = rowNode.nextSibling();
0372 
0373         int count = sheet->map()->increaseLoadedRowsCounter();
0374         if (updater && count >= 0) updater->setProgress(count);
0375     }
0376 
0377     // now recalculate the size for embedded shapes that had sizes specified relative to a bottom-right corner cell
0378     foreach (const ShapeLoadingData& sd, shapeData) {
0379         // subtract offset because the accumulated width and height we calculate below starts
0380         // at the top-left corner of this cell, but the shape can have an offset to that corner
0381         QSizeF size = QSizeF( sd.endPoint.x() - sd.offset.x(), sd.endPoint.y() - sd.offset.y());
0382         for (int col = sd.startCell.x(); col < sd.endCell.firstRange().left(); ++col)
0383             size += QSizeF(sheet->columnFormat(col)->width(), 0.0);
0384         if (sd.endCell.firstRange().top() > sd.startCell.y())
0385             size += QSizeF(0.0, sheet->rowFormats()->totalRowHeight(sd.startCell.y(), sd.endCell.firstRange().top() - 1));
0386         sd.shape->setSize(size);
0387     }
0388 
0389     QList<QPair<QRegion, Style> > styleRegions;
0390     QList<QPair<QRegion, Conditions> > conditionRegions;
0391     // insert the styles into the storage (column defaults)
0392     debugSheetsODF << "Inserting column default cell styles ...";
0393     loadSheetInsertStyles(sheet, autoStyles, columnStyleRegions, conditionalStyles,
0394                         QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions);
0395     // insert the styles into the storage (row defaults)
0396     debugSheetsODF << "Inserting row default cell styles ...";
0397     loadSheetInsertStyles(sheet, autoStyles, rowStyleRegions, conditionalStyles,
0398                         QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions);
0399     // insert the styles into the storage
0400     debugSheetsODF << "Inserting cell styles ...";
0401     loadSheetInsertStyles(sheet, autoStyles, cellStyleRegions, conditionalStyles,
0402                         QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions);
0403 
0404     sheet->cellStorage()->loadStyles(styleRegions);
0405     sheet->cellStorage()->loadConditions(conditionRegions);
0406 
0407     if (sheetElement.hasAttributeNS(KoXmlNS::table, "print-ranges")) {
0408         // e.g.: Sheet4.A1:Sheet4.E28
0409         QString range = sheetElement.attributeNS(KoXmlNS::table, "print-ranges", QString());
0410         Region region(loadRegion(range));
0411         if (!region.firstSheet() || sheet->sheetName() == region.firstSheet()->sheetName())
0412             sheet->printSettings()->setPrintRegion(region);
0413     }
0414 
0415     if (sheetElement.attributeNS(KoXmlNS::table, "protected", QString()) == "true") {
0416         loadProtection(sheet, sheetElement);
0417     }
0418     return true;
0419 }
0420 
0421 void Odf::loadSheetObject(Sheet *sheet, const KoXmlElement& element, KoShapeLoadingContext& shapeContext)
0422 {
0423     KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext);
0424     if (!shape)
0425         return;
0426     sheet->addShape(shape);
0427     dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(false);
0428 }
0429 
0430 void Odf::loadRowNodes(Sheet *sheet, const KoXmlElement& parent,
0431                             int& rowIndex,
0432                             int& maxColumn,
0433                             OdfLoadingContext& tableContext,
0434                             QHash<QString, QRegion>& rowStyleRegions,
0435                             QHash<QString, QRegion>& cellStyleRegions,
0436                             const IntervalMap<QString>& columnStyles,
0437                             const Styles& autoStyles,
0438                             QList<ShapeLoadingData>& shapeData
0439                             )
0440 {
0441     KoXmlNode node = parent.firstChild();
0442     while (!node.isNull()) {
0443         KoXmlElement elem = node.toElement();
0444         if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) {
0445             if (elem.localName() == "table-row") {
0446                 int columnMaximal = loadRowFormat(sheet, elem, rowIndex, tableContext,
0447                                                         rowStyleRegions, cellStyleRegions,
0448                                                         columnStyles, autoStyles, shapeData);
0449                 // allow the row to define more columns then defined via table-column
0450                 maxColumn = qMax(maxColumn, columnMaximal);
0451             } else if (elem.localName() == "table-row-group") {
0452                 loadRowNodes(sheet, elem, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData);
0453             }
0454         }
0455         node = node.nextSibling();
0456     }
0457 }
0458 
0459 void Odf::loadColumnNodes(Sheet *sheet, const KoXmlElement& parent,
0460                             int& indexCol,
0461                             int& maxColumn,
0462                             KoOdfLoadingContext& odfContext,
0463                             QHash<QString, QRegion>& columnStyleRegions,
0464                             IntervalMap<QString>& columnStyles
0465                             )
0466 {
0467     KoXmlNode node = parent.firstChild();
0468     while (!node.isNull()) {
0469         KoXmlElement elem = node.toElement();
0470         if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) {
0471             if (elem.localName() == "table-column") {
0472                 loadColumnFormat(sheet, elem, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles);
0473                 maxColumn = qMax(maxColumn, indexCol - 1);
0474             } else if (elem.localName() == "table-column-group") {
0475                 loadColumnNodes(sheet, elem, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles);
0476             }
0477         }
0478         node = node.nextSibling();
0479     }
0480 }
0481 
0482 
0483 void Odf::loadSheetInsertStyles(Sheet *sheet, const Styles& autoStyles,
0484                              const QHash<QString, QRegion>& styleRegions,
0485                              const QHash<QString, Conditions>& conditionalStyles,
0486                              const QRect& usedArea,
0487                              QList<QPair<QRegion, Style> >& outStyleRegions,
0488                              QList<QPair<QRegion, Conditions> >& outConditionalStyles)
0489 {
0490     const QList<QString> styleNames = styleRegions.keys();
0491     for (int i = 0; i < styleNames.count(); ++i) {
0492         if (!autoStyles.contains(styleNames[i]) && !sheet->map()->styleManager()->style(styleNames[i])) {
0493             warnSheetsODF << "\t" << styleNames[i] << " not used";
0494             continue;
0495         }
0496         const bool hasConditions = conditionalStyles.contains(styleNames[i]);
0497         const QRegion styleRegion = styleRegions[styleNames[i]] & QRegion(usedArea);
0498         if (hasConditions)
0499             outConditionalStyles.append(qMakePair(styleRegion, conditionalStyles[styleNames[i]]));
0500         if (autoStyles.contains(styleNames[i])) {
0501             //debugSheetsODF << "\tautomatic:" << styleNames[i] << " at" << styleRegion.rectCount() << "rects";
0502             Style style;
0503             style.setDefault(); // "overwrite" existing style
0504             style.merge(autoStyles[styleNames[i]]);
0505             outStyleRegions.append(qMakePair(styleRegion, style));
0506         } else {
0507             const CustomStyle* namedStyle = sheet->map()->styleManager()->style(styleNames[i]);
0508             //debugSheetsODF << "\tcustom:" << namedStyle->name() << " at" << styleRegion.rectCount() << "rects";
0509             Style style;
0510             style.setDefault(); // "overwrite" existing style
0511             style.merge(*namedStyle);
0512             outStyleRegions.append(qMakePair(styleRegion, style));
0513         }
0514     }
0515 }
0516 
0517 void Odf::replaceMacro(QString & text, const QString & old, const QString & newS)
0518 {
0519     int n = text.indexOf(old);
0520     if (n != -1)
0521         text = text.replace(n, old.length(), newS);
0522 }
0523 
0524 QString Odf::getPart(const KoXmlNode & part)
0525 {
0526     QString result;
0527     KoXmlElement e = KoXml::namedItemNS(part, KoXmlNS::text, "p");
0528     while (!e.isNull()) {
0529         QString text = e.text();
0530 
0531         KoXmlElement macro = KoXml::namedItemNS(e, KoXmlNS::text, "time");
0532         if (!macro.isNull())
0533             replaceMacro(text, macro.text(), "<time>");
0534 
0535         macro = KoXml::namedItemNS(e, KoXmlNS::text, "date");
0536         if (!macro.isNull())
0537             replaceMacro(text, macro.text(), "<date>");
0538 
0539         macro = KoXml::namedItemNS(e, KoXmlNS::text, "page-number");
0540         if (!macro.isNull())
0541             replaceMacro(text, macro.text(), "<page>");
0542 
0543         macro = KoXml::namedItemNS(e, KoXmlNS::text, "page-count");
0544         if (!macro.isNull())
0545             replaceMacro(text, macro.text(), "<pages>");
0546 
0547         macro = KoXml::namedItemNS(e, KoXmlNS::text, "sheet-name");
0548         if (!macro.isNull())
0549             replaceMacro(text, macro.text(), "<sheet>");
0550 
0551         macro = KoXml::namedItemNS(e, KoXmlNS::text, "title");
0552         if (!macro.isNull())
0553             replaceMacro(text, macro.text(), "<name>");
0554 
0555         macro = KoXml::namedItemNS(e, KoXmlNS::text, "file-name");
0556         if (!macro.isNull())
0557             replaceMacro(text, macro.text(), "<file>");
0558 
0559         //add support for multi line into kspread
0560         if (!result.isEmpty())
0561             result += '\n';
0562         result += text;
0563         e = e.nextSibling().toElement();
0564     }
0565 
0566     return result;
0567 }
0568 
0569 
0570 bool Odf::loadStyleFormat(Sheet *sheet, KoXmlElement *style)
0571 {
0572     QString hleft, hmiddle, hright;
0573     QString fleft, fmiddle, fright;
0574     KoXmlNode header = KoXml::namedItemNS(*style, KoXmlNS::style, "header");
0575 
0576     if (!header.isNull()) {
0577         debugSheetsODF << "Header exists";
0578         KoXmlNode part = KoXml::namedItemNS(header, KoXmlNS::style, "region-left");
0579         if (!part.isNull()) {
0580             hleft = getPart(part);
0581             debugSheetsODF << "Header left:" << hleft;
0582         } else
0583             debugSheetsODF << "Style:region:left doesn't exist!";
0584         part = KoXml::namedItemNS(header, KoXmlNS::style, "region-center");
0585         if (!part.isNull()) {
0586             hmiddle = getPart(part);
0587             debugSheetsODF << "Header middle:" << hmiddle;
0588         }
0589         part = KoXml::namedItemNS(header, KoXmlNS::style, "region-right");
0590         if (!part.isNull()) {
0591             hright = getPart(part);
0592             debugSheetsODF << "Header right:" << hright;
0593         }
0594         //If Header doesn't have region tag add it to Left
0595         hleft.append(getPart(header));
0596     }
0597     //TODO implement it under kspread
0598     KoXmlNode headerleft = KoXml::namedItemNS(*style, KoXmlNS::style, "header-left");
0599     if (!headerleft.isNull()) {
0600         KoXmlElement e = headerleft.toElement();
0601         if (e.hasAttributeNS(KoXmlNS::style, "display"))
0602             debugSheetsODF << "header.hasAttribute( style:display ) :" << e.hasAttributeNS(KoXmlNS::style, "display");
0603         else
0604             debugSheetsODF << "header left doesn't has attribute  style:display";
0605     }
0606     //TODO implement it under kspread
0607     KoXmlNode footerleft = KoXml::namedItemNS(*style, KoXmlNS::style, "footer-left");
0608     if (!footerleft.isNull()) {
0609         KoXmlElement e = footerleft.toElement();
0610         if (e.hasAttributeNS(KoXmlNS::style, "display"))
0611             debugSheetsODF << "footer.hasAttribute( style:display ) :" << e.hasAttributeNS(KoXmlNS::style, "display");
0612         else
0613             debugSheetsODF << "footer left doesn't has attribute  style:display";
0614     }
0615 
0616     KoXmlNode footer = KoXml::namedItemNS(*style, KoXmlNS::style, "footer");
0617 
0618     if (!footer.isNull()) {
0619         KoXmlNode part = KoXml::namedItemNS(footer, KoXmlNS::style, "region-left");
0620         if (!part.isNull()) {
0621             fleft = getPart(part);
0622             debugSheetsODF << "Footer left:" << fleft;
0623         }
0624         part = KoXml::namedItemNS(footer, KoXmlNS::style, "region-center");
0625         if (!part.isNull()) {
0626             fmiddle = getPart(part);
0627             debugSheetsODF << "Footer middle:" << fmiddle;
0628         }
0629         part = KoXml::namedItemNS(footer, KoXmlNS::style, "region-right");
0630         if (!part.isNull()) {
0631             fright = getPart(part);
0632             debugSheetsODF << "Footer right:" << fright;
0633         }
0634         //If Footer doesn't have region tag add it to Left
0635         fleft.append(getPart(footer));
0636     }
0637 
0638     sheet->print()->headerFooter()->setHeadFootLine(hleft, hmiddle, hright,
0639             fleft, fmiddle, fright);
0640     return true;
0641 }
0642 
0643 void Odf::loadMasterLayoutPage(Sheet *sheet, KoStyleStack &styleStack)
0644 {
0645     KoPageLayout pageLayout;
0646 
0647     if (styleStack.hasProperty(KoXmlNS::fo, "page-width")) {
0648         pageLayout.width = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "page-width"));
0649     }
0650     if (styleStack.hasProperty(KoXmlNS::fo, "page-height")) {
0651         pageLayout.height = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "page-height"));
0652     }
0653     if (styleStack.hasProperty(KoXmlNS::fo, "margin-top")) {
0654         pageLayout.topMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-top"));
0655     }
0656     if (styleStack.hasProperty(KoXmlNS::fo, "margin-bottom")) {
0657         pageLayout.bottomMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-bottom"));
0658     }
0659     if (styleStack.hasProperty(KoXmlNS::fo, "margin-left")) {
0660         pageLayout.leftMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-left"));
0661     }
0662     if (styleStack.hasProperty(KoXmlNS::fo, "margin-right")) {
0663         pageLayout.rightMargin = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-right"));
0664     }
0665     /* set sheet's direction to RTL if sheet name is an RTL string */
0666     Qt::LayoutDirection ldir = sheet->sheetName().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight;
0667     if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) {
0668         debugSheetsODF << "styleStack.hasAttribute( style:writing-mode ) :" << styleStack.hasProperty(KoXmlNS::style, "writing-mode");
0669         const QString writingMode = styleStack.property(KoXmlNS::style, "writing-mode");
0670         if (writingMode == "lr-tb") {
0671             ldir = Qt::LeftToRight;
0672         } else if (writingMode == "rl-tb") {
0673             ldir = Qt::RightToLeft;
0674         }
0675         //TODO
0676         //<value>lr-tb</value>
0677         //<value>rl-tb</value>
0678         //<value>tb-rl</value>
0679         //<value>tb-lr</value>
0680         //<value>lr</value>
0681         //<value>rl</value>
0682         //<value>tb</value>
0683         //<value>page</value>
0684     }
0685     sheet->setLayoutDirection(ldir);
0686 
0687     if (styleStack.hasProperty(KoXmlNS::style, "print-orientation")) {
0688         pageLayout.orientation = (styleStack.property(KoXmlNS::style, "print-orientation") == "landscape")
0689                                  ? KoPageFormat::Landscape : KoPageFormat::Portrait;
0690     }
0691     if (styleStack.hasProperty(KoXmlNS::style, "num-format")) {
0692         //not implemented into kspread
0693         //These attributes specify the numbering style to use.
0694         //If a numbering style is not specified, the numbering style is inherited from
0695         //the page style. See section 6.7.8 for information on these attributes
0696         debugSheetsODF << " num-format :" << styleStack.property(KoXmlNS::style, "num-format");
0697 
0698     }
0699     if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) {
0700         //TODO
0701         debugSheetsODF << " fo:background-color :" << styleStack.property(KoXmlNS::fo, "background-color");
0702     }
0703     if (styleStack.hasProperty(KoXmlNS::style, "print")) {
0704         //todo parsing
0705         QString str = styleStack.property(KoXmlNS::style, "print");
0706         debugSheetsODF << " style:print :" << str;
0707 
0708         if (str.contains("headers")) {
0709             //TODO implement it into kspread
0710         }
0711         if (str.contains("grid")) {
0712             sheet->print()->settings()->setPrintGrid(true);
0713         }
0714         if (str.contains("annotations")) {
0715             //TODO it's not implemented
0716         }
0717         if (str.contains("objects")) {
0718             //TODO it's not implemented
0719         }
0720         if (str.contains("charts")) {
0721             //TODO it's not implemented
0722         }
0723         if (str.contains("drawings")) {
0724             //TODO it's not implemented
0725         }
0726         if (str.contains("formulas")) {
0727             sheet->setShowFormula(true);
0728         }
0729         if (str.contains("zero-values")) {
0730             //TODO it's not implemented
0731         }
0732     }
0733     if (styleStack.hasProperty(KoXmlNS::style, "table-centering")) {
0734         QString str = styleStack.property(KoXmlNS::style, "table-centering");
0735         //TODO not implemented into kspread
0736         debugSheetsODF << " styleStack.attribute( style:table-centering ) :" << str;
0737 #if 0
0738         if (str == "horizontal") {
0739         } else if (str == "vertical") {
0740         } else if (str == "both") {
0741         } else if (str == "none") {
0742         } else
0743             debugSheetsODF << " table-centering unknown :" << str;
0744 #endif
0745     }
0746     sheet->print()->settings()->setPageLayout(pageLayout);
0747 }
0748 
0749 bool Odf::loadColumnFormat(Sheet *sheet, const KoXmlElement& column,
0750                              const KoOdfStylesReader& stylesReader, int & indexCol,
0751                              QHash<QString, QRegion>& columnStyleRegions, IntervalMap<QString>& columnStyles)
0752 {
0753 //   debugSheetsODF<<"bool Odf::loadColumnFormat(const KoXmlElement& column, const KoOdfStylesReader& stylesReader, unsigned int & indexCol ) index Col :"<<indexCol;
0754 
0755     bool isNonDefaultColumn = false;
0756 
0757     int number = 1;
0758     if (column.hasAttributeNS(KoXmlNS::table, "number-columns-repeated")) {
0759         bool ok = true;
0760         int n = column.attributeNS(KoXmlNS::table, "number-columns-repeated", QString()).toInt(&ok);
0761         if (ok)
0762             // Some spreadsheet programs may support more rows than Calligra Sheets so
0763             // limit the number of repeated rows.
0764             // FIXME POSSIBLE DATA LOSS!
0765             number = qMin(n, KS_colMax - indexCol + 1);
0766         //debugSheetsODF << "Repeated:" << number;
0767     }
0768 
0769     if (column.hasAttributeNS(KoXmlNS::table, "default-cell-style-name")) {
0770         const QString styleName = column.attributeNS(KoXmlNS::table, "default-cell-style-name", QString());
0771         if (!styleName.isEmpty()) {
0772             columnStyleRegions[styleName] += QRect(indexCol, 1, number, KS_rowMax);
0773             columnStyles.insert(indexCol, indexCol+number-1, styleName);
0774         }
0775     }
0776 
0777     enum { Visible, Collapsed, Filtered } visibility = Visible;
0778     if (column.hasAttributeNS(KoXmlNS::table, "visibility")) {
0779         const QString string = column.attributeNS(KoXmlNS::table, "visibility", "visible");
0780         if (string == "collapse")
0781             visibility = Collapsed;
0782         else if (string == "filter")
0783             visibility = Filtered;
0784         isNonDefaultColumn = true;
0785     }
0786 
0787     KoStyleStack styleStack;
0788     if (column.hasAttributeNS(KoXmlNS::table, "style-name")) {
0789         QString str = column.attributeNS(KoXmlNS::table, "style-name", QString());
0790         const KoXmlElement *style = stylesReader.findStyle(str, "table-column");
0791         if (style) {
0792             styleStack.push(*style);
0793             isNonDefaultColumn = true;
0794         }
0795     }
0796     styleStack.setTypeProperties("table-column"); //style for column
0797 
0798     double width = -1.0;
0799     if (styleStack.hasProperty(KoXmlNS::style, "column-width")) {
0800         width = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "column-width") , -1.0);
0801         //debugSheetsODF << " style:column-width : width :" << width;
0802         isNonDefaultColumn = true;
0803     }
0804 
0805     bool insertPageBreak = false;
0806     if (styleStack.hasProperty(KoXmlNS::fo, "break-before")) {
0807         QString str = styleStack.property(KoXmlNS::fo, "break-before");
0808         if (str == "page") {
0809             insertPageBreak = true;
0810         } else {
0811             // debugSheetsODF << " str :" << str;
0812         }
0813         isNonDefaultColumn = true;
0814     } else if (styleStack.hasProperty(KoXmlNS::fo, "break-after")) {
0815         // TODO
0816     }
0817 
0818     // If it's a default column, we can return here.
0819     // This saves the iteration, which can be caused by column cell default styles,
0820     // but which are not inserted here.
0821     if (!isNonDefaultColumn) {
0822         indexCol += number;
0823         return true;
0824     }
0825 
0826     for (int i = 0; i < number; ++i) {
0827         //debugSheetsODF << " insert new column: pos :" << indexCol << " width :" << width << " hidden ?" << visibility;
0828 
0829         if (isNonDefaultColumn) {
0830             ColumnFormat* cf = sheet->nonDefaultColumnFormat(indexCol);
0831 
0832             if (width != -1.0)   //safe
0833                 cf->setWidth(width);
0834             if (insertPageBreak) {
0835                 cf->setPageBreak(true);
0836             }
0837             if (visibility == Collapsed)
0838                 cf->setHidden(true);
0839             else if (visibility == Filtered)
0840                 cf->setFiltered(true);
0841 
0842             cf->setPageBreak(insertPageBreak);
0843         }
0844         ++indexCol;
0845     }
0846 //     debugSheetsODF<<" after index column !!!!!!!!!!!!!!!!!! :"<<indexCol;
0847     return true;
0848 }
0849 
0850 int Odf::loadRowFormat(Sheet *sheet, const KoXmlElement& row, int &rowIndex,
0851                           OdfLoadingContext& tableContext,
0852                           QHash<QString, QRegion>& rowStyleRegions,
0853                           QHash<QString, QRegion>& cellStyleRegions,
0854                           const IntervalMap<QString>& columnStyles,
0855                           const Styles& autoStyles,
0856                           QList<ShapeLoadingData>& shapeData)
0857 {
0858     static const QString sStyleName             = QString::fromLatin1("style-name");
0859     static const QString sNumberRowsRepeated    = QString::fromLatin1("number-rows-repeated");
0860     static const QString sDefaultCellStyleName  = QString::fromLatin1("default-cell-style-name");
0861     static const QString sVisibility            = QString::fromLatin1("visibility");
0862     static const QString sVisible               = QString::fromLatin1("visible");
0863     static const QString sCollapse              = QString::fromLatin1("collapse");
0864     static const QString sFilter                = QString::fromLatin1("filter");
0865     static const QString sPage                  = QString::fromLatin1("page");
0866     static const QString sTableCell             = QString::fromLatin1("table-cell");
0867     static const QString sCoveredTableCell      = QString::fromLatin1("covered-table-cell");
0868     static const QString sNumberColumnsRepeated = QString::fromLatin1("number-columns-repeated");
0869 
0870 //    debugSheetsODF<<"Odf::loadRowFormat( const KoXmlElement& row, int &rowIndex,const KoOdfStylesReader& stylesReader, bool isLast )***********";
0871     KoOdfLoadingContext& odfContext = tableContext.odfContext;
0872     bool isNonDefaultRow = false;
0873 
0874     KoStyleStack styleStack;
0875     if (row.hasAttributeNS(KoXmlNS::table, sStyleName)) {
0876         QString str = row.attributeNS(KoXmlNS::table, sStyleName, QString());
0877         const KoXmlElement *style = odfContext.stylesReader().findStyle(str, "table-row");
0878         if (style) {
0879             styleStack.push(*style);
0880             isNonDefaultRow = true;
0881         }
0882     }
0883     styleStack.setTypeProperties("table-row");
0884 
0885     int number = 1;
0886     if (row.hasAttributeNS(KoXmlNS::table, sNumberRowsRepeated)) {
0887         bool ok = true;
0888         int n = row.attributeNS(KoXmlNS::table, sNumberRowsRepeated, QString()).toInt(&ok);
0889         if (ok)
0890             // Some spreadsheet programs may support more rows than Calligra Sheets so
0891             // limit the number of repeated rows.
0892             // FIXME POSSIBLE DATA LOSS!
0893             number = qMin(n, KS_rowMax - rowIndex + 1);
0894     }
0895 
0896     QString rowCellStyleName;
0897     if (row.hasAttributeNS(KoXmlNS::table, sDefaultCellStyleName)) {
0898         rowCellStyleName = row.attributeNS(KoXmlNS::table, sDefaultCellStyleName, QString());
0899         if (!rowCellStyleName.isEmpty()) {
0900             rowStyleRegions[rowCellStyleName] += QRect(1, rowIndex, KS_colMax, number);
0901         }
0902     }
0903 
0904     double height = -1.0;
0905     if (styleStack.hasProperty(KoXmlNS::style, "row-height")) {
0906         height = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "row-height") , -1.0);
0907         //    debugSheetsODF<<" properties style:row-height : height :"<<height;
0908         isNonDefaultRow = true;
0909     }
0910 
0911     enum { Visible, Collapsed, Filtered } visibility = Visible;
0912     if (row.hasAttributeNS(KoXmlNS::table, sVisibility)) {
0913         const QString string = row.attributeNS(KoXmlNS::table, sVisibility, sVisible);
0914         if (string == sCollapse)
0915             visibility = Collapsed;
0916         else if (string == sFilter)
0917             visibility = Filtered;
0918         isNonDefaultRow = true;
0919     }
0920 
0921     bool insertPageBreak = false;
0922     if (styleStack.hasProperty(KoXmlNS::fo, "break-before")) {
0923         QString str = styleStack.property(KoXmlNS::fo, "break-before");
0924         if (str == sPage) {
0925             insertPageBreak = true;
0926         }
0927         //  else
0928         //      debugSheetsODF<<" str :"<<str;
0929         isNonDefaultRow = true;
0930     } else if (styleStack.hasProperty(KoXmlNS::fo, "break-after")) {
0931         // TODO
0932     }
0933 
0934 //     debugSheetsODF<<" create non defaultrow format :"<<rowIndex<<" repeate :"<<number<<" height :"<<height;
0935     if (isNonDefaultRow) {
0936         if (height != -1.0)
0937             sheet->rowFormats()->setRowHeight(rowIndex, rowIndex + number - 1, height);
0938         sheet->rowFormats()->setPageBreak(rowIndex, rowIndex + number - 1, insertPageBreak);
0939         if (visibility == Collapsed)
0940             sheet->rowFormats()->setHidden(rowIndex, rowIndex + number - 1, true);
0941         else if (visibility == Filtered)
0942             sheet->rowFormats()->setFiltered(rowIndex, rowIndex + number - 1, true);
0943     }
0944 
0945     int columnIndex = 1;
0946     int columnMaximal = 0;
0947     const int endRow = qMin(rowIndex + number - 1, KS_rowMax);
0948 
0949     KoXmlElement cellElement;
0950     forEachElement(cellElement, row) {
0951         if (cellElement.namespaceURI() != KoXmlNS::table)
0952             continue;
0953         if (cellElement.localName() != sTableCell && cellElement.localName() != sCoveredTableCell)
0954             continue;
0955 
0956 
0957         bool ok = false;
0958         const int n = cellElement.attributeNS(KoXmlNS::table, sNumberColumnsRepeated, QString()).toInt(&ok);
0959         // Some spreadsheet programs may support more columns than
0960         // Calligra Sheets so limit the number of repeated columns.
0961         const int numberColumns = ok ? qMin(n, KS_colMax - columnIndex + 1) : 1;
0962         columnMaximal = qMax(numberColumns, columnMaximal);
0963 
0964         // Styles are inserted at the end of the loading process, so check the XML directly here.
0965         const QString styleName = cellElement.attributeNS(KoXmlNS::table , sStyleName, QString());
0966         if (!styleName.isEmpty())
0967             cellStyleRegions[styleName] += QRect(columnIndex, rowIndex, numberColumns, number);
0968 
0969         // figure out exact cell style for loading of cell content
0970         QString cellStyleName = styleName;
0971         if (cellStyleName.isEmpty())
0972             cellStyleName = rowCellStyleName;
0973         if (cellStyleName.isEmpty())
0974             cellStyleName = columnStyles.get(columnIndex);
0975 
0976         Cell cell(sheet, columnIndex, rowIndex);
0977         loadCell(&cell, cellElement, tableContext, autoStyles, cellStyleName, shapeData);
0978 
0979         if (!cell.comment().isEmpty())
0980             sheet->cellStorage()->setComment(Region(columnIndex, rowIndex, numberColumns, number, sheet), cell.comment());
0981         if (!cell.conditions().isEmpty())
0982             sheet->cellStorage()->setConditions(Region(columnIndex, rowIndex, numberColumns, number, sheet), cell.conditions());
0983         if (!cell.validity().isEmpty())
0984             sheet->cellStorage()->setValidity(Region(columnIndex, rowIndex, numberColumns, number, sheet), cell.validity());
0985 
0986         if (!cell.hasDefaultContent()) {
0987             // Row-wise filling of PointStorages is faster than column-wise filling.
0988             QSharedPointer<QTextDocument> richText = cell.richText();
0989             for (int r = rowIndex; r <= endRow; ++r) {
0990                 for (int c = 0; c < numberColumns; ++c) {
0991                     Cell target(sheet, columnIndex + c, r);
0992                     target.setFormula(cell.formula());
0993                     target.setUserInput(cell.userInput());
0994                     target.setRichText(richText);
0995                     target.setValue(cell.value());
0996                     if (cell.doesMergeCells()) {
0997                         target.mergeCells(columnIndex + c, r, cell.mergedXCells(), cell.mergedYCells());
0998                     }
0999                 }
1000             }
1001         }
1002         columnIndex += numberColumns;
1003     }
1004 
1005     sheet->cellStorage()->setRowsRepeated(rowIndex, number);
1006 
1007     rowIndex += number;
1008     return columnMaximal;
1009 }
1010 
1011 
1012 // *************** Saving *****************
1013 
1014 bool Odf::saveSheet(Sheet *sheet, OdfSavingContext& tableContext)
1015 {
1016     KoXmlWriter & xmlWriter = tableContext.shapeContext.xmlWriter();
1017     KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
1018     xmlWriter.startElement("table:table");
1019     xmlWriter.addAttribute("table:name", sheet->sheetName());
1020     xmlWriter.addAttribute("table:style-name", saveSheetStyleName(sheet, mainStyles));
1021     QByteArray pwd;
1022     sheet->password(pwd);
1023     if (!pwd.isNull()) {
1024         xmlWriter.addAttribute("table:protected", "true");
1025         QByteArray str = KCodecs::base64Encode(pwd);
1026         // FIXME Stefan: see OpenDocument spec, ch. 17.3 Encryption
1027         xmlWriter.addAttribute("table:protection-key", QString(str));
1028     }
1029     QRect _printRange = sheet->printSettings()->printRegion().lastRange();
1030     if (!_printRange.isNull() &&_printRange != (QRect(QPoint(1, 1), QPoint(KS_colMax, KS_rowMax)))) {
1031         const Region region(_printRange, sheet);
1032         if (region.isValid()) {
1033             debugSheetsODF << region;
1034             xmlWriter.addAttribute("table:print-ranges", saveRegion(region.name()));
1035         }
1036     }
1037 
1038     // flake
1039     // Create a dict of cell anchored shapes with the cell as key.
1040     int sheetAnchoredCount = 0;
1041     foreach(KoShape* shape, sheet->shapes()) {
1042         if (dynamic_cast<ShapeApplicationData*>(shape->applicationData())->isAnchoredToCell()) {
1043             qreal dummy;
1044             const QPointF position = shape->position();
1045             const int col = sheet->leftColumn(position.x(), dummy);
1046             const int row = sheet->topRow(position.y(), dummy);
1047             tableContext.insertCellAnchoredShape(sheet, row, col, shape);
1048         } else {
1049             sheetAnchoredCount++;
1050         }
1051     }
1052 
1053     // flake
1054     // Save the remaining shapes, those that are anchored in the page.
1055     if (sheetAnchoredCount) {
1056         xmlWriter.startElement("table:shapes");
1057         foreach(KoShape* shape, sheet->shapes()) {
1058             if (dynamic_cast<ShapeApplicationData*>(shape->applicationData())->isAnchoredToCell())
1059                 continue;
1060             shape->saveOdf(tableContext.shapeContext);
1061         }
1062         xmlWriter.endElement();
1063     }
1064 
1065     const QRect usedArea = sheet->usedArea();
1066     saveColRowCell(sheet, usedArea.width(), usedArea.height(), tableContext);
1067 
1068     xmlWriter.endElement();
1069     return true;
1070 }
1071 
1072 QString Odf::saveSheetStyleName(Sheet *sheet, KoGenStyles &mainStyles)
1073 {
1074     KoGenStyle pageStyle(KoGenStyle::TableAutoStyle, "table"/*FIXME I don't know if name is sheet*/);
1075 
1076     KoGenStyle pageMaster(KoGenStyle::MasterPageStyle);
1077     const QString pageLayoutName = savePageLayout(sheet->printSettings(), mainStyles,
1078                                    sheet->getShowFormula(),
1079                                    !sheet->getHideZero());
1080     pageMaster.addAttribute("style:page-layout-name", pageLayoutName);
1081 
1082     QBuffer buffer;
1083     buffer.open(QIODevice::WriteOnly);
1084     KoXmlWriter elementWriter(&buffer);    // TODO pass indentation level
1085     saveHeaderFooter(sheet, elementWriter);
1086 
1087     QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
1088     pageMaster.addChildElement("headerfooter", elementContents);
1089     pageStyle.addAttribute("style:master-page-name", mainStyles.insert(pageMaster, "Standard"));
1090 
1091     pageStyle.addProperty("table:display", !sheet->isHidden());
1092 
1093     if( !sheet->backgroundImage().isNull() ) {
1094         QBuffer bgBuffer;
1095         bgBuffer.open(QIODevice::WriteOnly);
1096         KoXmlWriter bgWriter(&bgBuffer); //TODO pass indentation level
1097         saveBackgroundImage(sheet, bgWriter);
1098 
1099         const QString bgContent = QString::fromUtf8(bgBuffer.buffer(), bgBuffer.size());
1100         pageMaster.addChildElement("backgroundImage", bgContent);
1101     }
1102 
1103     return mainStyles.insert(pageStyle, "ta");
1104 }
1105 
1106 void Odf::saveColRowCell(Sheet *sheet, int maxCols, int maxRows, OdfSavingContext& tableContext)
1107 {
1108     debugSheetsODF << "Odf::saveColRowCell:" << sheet->sheetName();
1109 
1110     KoXmlWriter & xmlWriter = tableContext.shapeContext.xmlWriter();
1111     KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
1112 
1113     // calculate the column/row default cell styles
1114     int maxMaxRows = maxRows; // includes the max row a column default style occupies
1115     // also extends the maximum column/row to include column/row styles
1116     sheet->styleStorage()->saveCreateDefaultStyles(maxCols, maxMaxRows, tableContext.columnDefaultStyles, tableContext.rowDefaultStyles);
1117     if (tableContext.rowDefaultStyles.count() != 0)
1118         maxRows = qMax(maxRows, (--tableContext.rowDefaultStyles.constEnd()).key());
1119     // Take the actual used area into account so we also catch shapes that are
1120     // anchored after any content.
1121     QRect r = sheet->usedArea(false);
1122     maxRows = qMax(maxRows, r.bottom());
1123     maxCols = qMax(maxCols, r.right());
1124     // OpenDocument needs at least one cell per sheet.
1125     maxCols = qMin(KS_colMax, qMax(1, maxCols));
1126     maxRows = qMin(KS_rowMax, qMax(1, maxRows));
1127     maxMaxRows = maxMaxRows;
1128     debugSheetsODF << "\t Sheet dimension:" << maxCols << " x" << maxRows;
1129 
1130     // saving the columns
1131     //
1132     int i = 1;
1133     while (i <= maxCols) {
1134         const ColumnFormat* column = sheet->columnFormat(i);
1135 //         debugSheetsODF << "Odf::saveColRowCell: first col loop:"
1136 //                       << "i:" << i
1137 //                       << "column:" << (column ? column->column() : 0)
1138 //                       << "default:" << (column ? column->isDefault() : false);
1139 
1140         //style default layout for column
1141         const Style style = tableContext.columnDefaultStyles.value(i);
1142 
1143         int j = i;
1144         int count = 1;
1145 
1146         while (j <= maxCols) {
1147             const ColumnFormat* nextColumn = sheet->nextColumn(j);
1148             const int nextColumnIndex = nextColumn ? nextColumn->column() : 0;
1149             const QMap<int, Style>::iterator nextColumnDefaultStyle = tableContext.columnDefaultStyles.upperBound(j);
1150             const int nextStyleColumnIndex = nextColumnDefaultStyle == tableContext.columnDefaultStyles.end()
1151                                              ? 0 : nextColumnDefaultStyle.key();
1152             // j becomes the index of the adjacent column
1153             ++j;
1154 
1155 //           debugSheetsODF <<"Odf::saveColRowCell: second col loop:"
1156 //                         << "j:" << j
1157 //                         << "next column:" << (nextColumn ? nextColumn->column() : 0)
1158 //                         << "next styled column:" << nextStyleColumnIndex;
1159 
1160             // no next or not the adjacent column?
1161             if ((!nextColumn && !nextStyleColumnIndex) ||
1162                     (nextColumnIndex != j && nextStyleColumnIndex != j)) {
1163                 // if the origin column was a default column,
1164                 if (column->isDefault() && style.isDefault()) {
1165                     // we count the default columns
1166                     if (!nextColumn && !nextStyleColumnIndex)
1167                         count = maxCols - i + 1;
1168                     else if (nextColumn && (!nextStyleColumnIndex || nextColumn->column() <= nextStyleColumnIndex))
1169                         count = nextColumn->column() - i;
1170                     else
1171                         count = nextStyleColumnIndex - i;
1172                 }
1173                 // otherwise we just stop here to process the adjacent
1174                 // column in the next iteration of the outer loop
1175                 break;
1176             }
1177 
1178             // stop, if the next column differs from the current one
1179             if ((nextColumn && (*column != *nextColumn)) || (!nextColumn && !column->isDefault()))
1180                 break;
1181             if (style != tableContext.columnDefaultStyles.value(j))
1182                 break;
1183             ++count;
1184         }
1185 
1186         xmlWriter.startElement("table:table-column");
1187         if (!column->isDefault()) {
1188             KoGenStyle currentColumnStyle(KoGenStyle::TableColumnAutoStyle, "table-column");
1189             currentColumnStyle.addPropertyPt("style:column-width", column->width());
1190             if (column->hasPageBreak()) {
1191                 currentColumnStyle.addProperty("fo:break-before", "page");
1192             }
1193             xmlWriter.addAttribute("table:style-name", mainStyles.insert(currentColumnStyle, "co"));
1194         }
1195         if (!column->isDefault() || !style.isDefault()) {
1196             if (!style.isDefault()) {
1197                 KoGenStyle currentDefaultCellStyle; // the type is determined in saveOdfStyle
1198                 const QString name = saveStyle(&style, currentDefaultCellStyle, mainStyles,
1199                                                    sheet->map()->styleManager());
1200                 xmlWriter.addAttribute("table:default-cell-style-name", name);
1201             }
1202 
1203             if (column->isHidden())
1204                 xmlWriter.addAttribute("table:visibility", "collapse");
1205             else if (column->isFiltered())
1206                 xmlWriter.addAttribute("table:visibility", "filter");
1207         }
1208         if (count > 1)
1209             xmlWriter.addAttribute("table:number-columns-repeated", count);
1210         xmlWriter.endElement();
1211 
1212         debugSheetsODF << "Odf::saveColRowCell: column" << i
1213         << "repeated" << count - 1 << "time(s)";
1214 
1215         i += count;
1216     }
1217 
1218     // saving the rows and the cells
1219     // we have to loop through all rows of the used area
1220     for (i = 1; i <= maxRows; ++i) {
1221         // default cell style for row
1222         const Style style = tableContext.rowDefaultStyles.value(i);
1223 
1224         xmlWriter.startElement("table:table-row");
1225 
1226         const bool rowIsDefault = sheet->rowFormats()->isDefaultRow(i);
1227         if (!rowIsDefault) {
1228             KoGenStyle currentRowStyle(KoGenStyle::TableRowAutoStyle, "table-row");
1229             currentRowStyle.addPropertyPt("style:row-height", sheet->rowFormats()->rowHeight(i));
1230             if (sheet->rowFormats()->hasPageBreak(i)) {
1231                 currentRowStyle.addProperty("fo:break-before", "page");
1232             }
1233             xmlWriter.addAttribute("table:style-name", mainStyles.insert(currentRowStyle, "ro"));
1234         }
1235 
1236         // We cannot use cellStorage()->rowRepeat(i) here cause the RowRepeatStorage only knows
1237         // about the content but not about the shapes anchored to a cell. So, we need to check
1238         // for them here to be sure to catch them even when the content in the cell is repeated.
1239         int repeated = 1;
1240         // empty row?
1241         if (!sheet->cellStorage()->firstInRow(i) && !tableContext.rowHasCellAnchoredShapes(sheet, i)) { // row is empty
1242 //             debugSheetsODF <<"Odf::saveColRowCell: first row loop:"
1243 //                           << " i: " << i
1244 //                           << " row: " << row->row();
1245             int j = i + 1;
1246 
1247             // search for
1248             //   next non-empty row
1249             // or
1250             //   next row with different Format
1251             while (j <= maxRows && !sheet->cellStorage()->firstInRow(j) && !tableContext.rowHasCellAnchoredShapes(sheet, j)) {
1252 //               debugSheetsODF <<"Odf::saveColRowCell: second row loop:"
1253 //                         << " j: " << j
1254 //                         << " row: " << nextRow->row();
1255 
1256                 // if the reference row has the default row format
1257                 if (rowIsDefault && style.isDefault()) {
1258                     // if the next is not default, stop here
1259                     if (!sheet->rowFormats()->isDefaultRow(j) || !tableContext.rowDefaultStyles.value(j).isDefault())
1260                         break;
1261                     // otherwise, jump to the next
1262                     ++j;
1263                     continue;
1264                 }
1265 
1266                 // stop, if the next row differs from the current one
1267                 if (!sheet->rowFormats()->rowsAreEqual(i, j))
1268                     break;
1269                 if (style != tableContext.rowDefaultStyles.value(j))
1270                     break;
1271                 // otherwise, process the next
1272                 ++j;
1273             }
1274             repeated = j - i;
1275 
1276             if (repeated > 1)
1277                 xmlWriter.addAttribute("table:number-rows-repeated", repeated);
1278             if (!style.isDefault()) {
1279                 KoGenStyle currentDefaultCellStyle; // the type is determined in saveCellStyle
1280                 const QString name = saveStyle(&style, currentDefaultCellStyle, mainStyles,
1281                                                    sheet->map()->styleManager());
1282                 xmlWriter.addAttribute("table:default-cell-style-name", name);
1283             }
1284             if (sheet->rowFormats()->isHidden(i))   // never true for the default row
1285                 xmlWriter.addAttribute("table:visibility", "collapse");
1286             else if (sheet->rowFormats()->isFiltered(i)) // never true for the default row
1287                 xmlWriter.addAttribute("table:visibility", "filter");
1288 
1289             // NOTE Stefan: Even if paragraph 8.1 states, that rows may be empty, the
1290             //              RelaxNG schema does not allow that.
1291             xmlWriter.startElement("table:table-cell");
1292             // Fill the row with empty cells, if there's a row default cell style.
1293             if (!style.isDefault())
1294                 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(maxCols));
1295             // Fill the row with empty cells up to the last column with a default cell style.
1296             else if (!tableContext.columnDefaultStyles.isEmpty()) {
1297                 const int col = (--tableContext.columnDefaultStyles.constEnd()).key();
1298                 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(col));
1299             }
1300             xmlWriter.endElement();
1301 
1302             debugSheetsODF << "Odf::saveColRowCell: empty row" << i
1303             << "repeated" << repeated << "time(s)";
1304 
1305             // copy the index for the next row to process
1306             i = j - 1; /*it's already incremented in the for loop*/
1307         } else { // row is not empty
1308             if (!style.isDefault()) {
1309                 KoGenStyle currentDefaultCellStyle; // the type is determined in saveCellStyle
1310                 const QString name = saveStyle(&style, currentDefaultCellStyle, mainStyles,
1311                                                    sheet->map()->styleManager());
1312                 xmlWriter.addAttribute("table:default-cell-style-name", name);
1313             }
1314             if (sheet->rowFormats()->isHidden(i))   // never true for the default row
1315                 xmlWriter.addAttribute("table:visibility", "collapse");
1316             else if (sheet->rowFormats()->isFiltered(i)) // never true for the default row
1317                 xmlWriter.addAttribute("table:visibility", "filter");
1318 
1319             int j = i + 1;
1320             while (j <= maxRows && compareRows(sheet, i, j, maxCols, tableContext)) {
1321                 j++;
1322                 repeated++;
1323             }
1324             repeated = j - i;
1325             if (repeated > 1) {
1326                 debugSheetsODF << "Odf::saveColRowCell: NON-empty row" << i
1327                 << "repeated" << repeated << "times";
1328 
1329                 xmlWriter.addAttribute("table:number-rows-repeated", repeated);
1330             }
1331 
1332             saveCells(sheet, i, maxCols, tableContext);
1333 
1334             // copy the index for the next row to process
1335             i = j - 1; /*it's already incremented in the for loop*/
1336         }
1337         xmlWriter.endElement();
1338     }
1339 
1340     // Fill in rows with empty cells, if there's a column default cell style.
1341     if (!tableContext.columnDefaultStyles.isEmpty()) {
1342         if (maxMaxRows > maxRows) {
1343             xmlWriter.startElement("table:table-row");
1344             if (maxMaxRows > maxRows + 1)
1345                 xmlWriter.addAttribute("table:number-rows-repeated", maxMaxRows - maxRows);
1346             xmlWriter.startElement("table:table-cell");
1347             const int col = qMin(maxCols, (--tableContext.columnDefaultStyles.constEnd()).key());
1348             xmlWriter.addAttribute("table:number-columns-repeated", QString::number(col));
1349             xmlWriter.endElement();
1350             xmlWriter.endElement();
1351         }
1352     }
1353 }
1354 
1355 void Odf::saveCells(Sheet *sheet, int row, int maxCols, OdfSavingContext& tableContext)
1356 {
1357     KoXmlWriter & xmlWriter = tableContext.shapeContext.xmlWriter();
1358 
1359     int i = 1;
1360     Cell cell(sheet, i, row);
1361     Cell nextCell = sheet->cellStorage()->nextInRow(i, row);
1362     // handle situations where the row contains shapes and nothing else
1363     if (cell.isDefault() && nextCell.isNull()) {
1364         int nextShape = tableContext.nextAnchoredShape(sheet, row, i);
1365         if (nextShape)
1366             nextCell = Cell(sheet, nextShape, row);
1367     }
1368     // while
1369     //   the current cell is not a default one
1370     // or
1371     //   we have a further cell in this row
1372     do {
1373 //         debugSheetsODF <<"Odf::saveCells:"
1374 //                       << " i: " << i
1375 //                       << " column: " << cell.column() << endl;
1376 
1377         int repeated = 1;
1378         int column = i;
1379         saveCell(&cell, repeated, tableContext);
1380         i += repeated;
1381         // stop if we reached the end column
1382         if (i > maxCols || nextCell.isNull())
1383             break;
1384 
1385         cell = Cell(sheet, i, row);
1386         // if we have a shape anchored to an empty cell, ensure that the cell gets also processed
1387         int nextShape = tableContext.nextAnchoredShape(sheet, row, column);
1388         if (nextShape && ((nextShape < i) || cell.isDefault())) {
1389             cell = Cell(sheet, nextShape, row);
1390             i = nextShape;
1391         }
1392 
1393         nextCell = sheet->cellStorage()->nextInRow(i, row);
1394     } while (!cell.isDefault() || tableContext.cellHasAnchoredShapes(sheet, cell.row(), cell.column()) || !nextCell.isNull());
1395 
1396     // Fill the row with empty cells, if there's a row default cell style.
1397     if (tableContext.rowDefaultStyles.contains(row)) {
1398         if (maxCols >= i) {
1399             xmlWriter.startElement("table:table-cell");
1400             if (maxCols > i)
1401                 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(maxCols - i + 1));
1402             xmlWriter.endElement();
1403         }
1404     }
1405     // Fill the row with empty cells up to the last column with a default cell style.
1406     else if (!tableContext.columnDefaultStyles.isEmpty()) {
1407         const int col = (--tableContext.columnDefaultStyles.constEnd()).key();
1408         if (col >= i) {
1409             xmlWriter.startElement("table:table-cell");
1410             if (col > i)
1411                 xmlWriter.addAttribute("table:number-columns-repeated", QString::number(col - i + 1));
1412             xmlWriter.endElement();
1413         }
1414     }
1415 }
1416 
1417 void Odf::saveBackgroundImage(Sheet *sheet, KoXmlWriter& xmlWriter)
1418 {
1419     const Sheet::BackgroundImageProperties& properties = sheet->backgroundImageProperties();
1420     xmlWriter.startElement("style:backgroundImage");
1421 
1422     //xmlWriter.addAttribute("xlink:href", fileName);
1423     xmlWriter.addAttribute("xlink:type", "simple");
1424     xmlWriter.addAttribute("xlink:show", "embed");
1425     xmlWriter.addAttribute("xlink:actuate", "onLoad");
1426 
1427     QString opacity = QString("%1%").arg(properties.opacity);
1428     xmlWriter.addAttribute("draw:opacity", opacity);
1429 
1430     QString position;
1431     if(properties.horizontalPosition == Sheet::BackgroundImageProperties::Left) {
1432         position += "left";
1433     }
1434     else if(properties.horizontalPosition == Sheet::BackgroundImageProperties::HorizontalCenter) {
1435         position += "center";
1436     }
1437     else if(properties.horizontalPosition == Sheet::BackgroundImageProperties::Right) {
1438         position += "right";
1439     }
1440 
1441     position += ' ';
1442 
1443     if(properties.verticalPosition == Sheet::BackgroundImageProperties::Top) {
1444         position += "top";
1445     }
1446     else if(properties.verticalPosition == Sheet::BackgroundImageProperties::VerticalCenter) {
1447         position += "center";
1448     }
1449     else if(properties.verticalPosition == Sheet::BackgroundImageProperties::Bottom) {
1450         position += "right";
1451     }
1452     xmlWriter.addAttribute("style:position", position);
1453 
1454     QString repeat;
1455     if(properties.repeat == Sheet::BackgroundImageProperties::NoRepeat) {
1456         repeat = "no-repeat";
1457     }
1458     else if(properties.repeat == Sheet::BackgroundImageProperties::Repeat) {
1459         repeat = "repeat";
1460     }
1461     else if(properties.repeat == Sheet::BackgroundImageProperties::Stretch) {
1462         repeat = "stretch";
1463     }
1464     xmlWriter.addAttribute("style:repeat", repeat);
1465 
1466     xmlWriter.endElement();
1467 }
1468 
1469 void Odf::addText(const QString & text, KoXmlWriter & writer)
1470 {
1471     if (!text.isEmpty())
1472         writer.addTextNode(text);
1473 }
1474 
1475 void Odf::convertPart(Sheet *sheet, const QString & part, KoXmlWriter & xmlWriter)
1476 {
1477     QString text;
1478     QString var;
1479 
1480     bool inVar = false;
1481     uint i = 0;
1482     uint l = part.length();
1483     while (i < l) {
1484         if (inVar || part[i] == '<') {
1485             inVar = true;
1486             var += part[i];
1487             if (part[i] == '>') {
1488                 inVar = false;
1489                 if (var == "<page>") {
1490                     addText(text, xmlWriter);
1491                     xmlWriter.startElement("text:page-number");
1492                     xmlWriter.addTextNode("1");
1493                     xmlWriter.endElement();
1494                 } else if (var == "<pages>") {
1495                     addText(text, xmlWriter);
1496                     xmlWriter.startElement("text:page-count");
1497                     xmlWriter.addTextNode("99");   //TODO I think that it can be different from 99
1498                     xmlWriter.endElement();
1499                 } else if (var == "<date>") {
1500                     addText(text, xmlWriter);
1501                     //text:p><text:date style:data-style-name="N2" text:date-value="2005-10-02">02/10/2005</text:date>, <text:time>10:20:12</text:time></text:p> "add style" => create new style
1502 #if 0 //FIXME
1503                     KoXmlElement t = dd.createElement("text:date");
1504                     t.setAttribute("text:date-value", "0-00-00");
1505                     // todo: "style:data-style-name", "N2"
1506                     t.appendChild(dd.createTextNode(QDate::currentDate().toString()));
1507                     parent.appendChild(t);
1508 #endif
1509                 } else if (var == "<time>") {
1510                     addText(text, xmlWriter);
1511 
1512                     xmlWriter.startElement("text:time");
1513                     xmlWriter.addTextNode(QTime::currentTime().toString());
1514                     xmlWriter.endElement();
1515                 } else if (var == "<file>") { // filepath + name
1516                     addText(text, xmlWriter);
1517                     xmlWriter.startElement("text:file-name");
1518                     xmlWriter.addAttribute("text:display", "full");
1519                     xmlWriter.addTextNode("???");
1520                     xmlWriter.endElement();
1521                 } else if (var == "<name>") { // filename
1522                     addText(text, xmlWriter);
1523 
1524                     xmlWriter.startElement("text:title");
1525                     xmlWriter.addTextNode("???");
1526                     xmlWriter.endElement();
1527                 } else if (var == "<author>") {
1528                     DocBase* sdoc = sheet->doc();
1529                     KoDocumentInfo* docInfo = sdoc->documentInfo();
1530 
1531                     text += docInfo->authorInfo("creator");
1532                     addText(text, xmlWriter);
1533                 } else if (var == "<email>") {
1534                     DocBase* sdoc = sheet->doc();
1535                     KoDocumentInfo* docInfo = sdoc->documentInfo();
1536 
1537                     text += docInfo->authorInfo("email");
1538                     addText(text, xmlWriter);
1539 
1540                 } else if (var == "<org>") {
1541                     DocBase* sdoc = sheet->doc();
1542                     KoDocumentInfo* docInfo    = sdoc->documentInfo();
1543 
1544                     text += docInfo->authorInfo("company");
1545                     addText(text, xmlWriter);
1546 
1547                 } else if (var == "<sheet>") {
1548                     addText(text, xmlWriter);
1549 
1550                     xmlWriter.startElement("text:sheet-name");
1551                     xmlWriter.addTextNode("???");
1552                     xmlWriter.endElement();
1553                 } else {
1554                     // no known variable:
1555                     text += var;
1556                     addText(text, xmlWriter);
1557                 }
1558 
1559                 text.clear();
1560                 var.clear();
1561             }
1562         } else {
1563             text += part[i];
1564         }
1565         ++i;
1566     }
1567     if (!text.isEmpty() || !var.isEmpty()) {
1568         //we don't have var at the end =>store it
1569         addText(text + var, xmlWriter);
1570     }
1571     debugSheetsODF << " text end :" << text << " var :" << var;
1572 }
1573 
1574 void Odf::saveHeaderFooter(Sheet *sheet, KoXmlWriter &xmlWriter)
1575 {
1576     HeaderFooter *hf = sheet->print()->headerFooter();
1577     QString headerLeft = hf->headLeft();
1578     QString headerCenter = hf->headMid();
1579     QString headerRight = hf->headRight();
1580 
1581     QString footerLeft = hf->footLeft();
1582     QString footerCenter = hf->footMid();
1583     QString footerRight =hf->footRight();
1584 
1585     xmlWriter.startElement("style:header");
1586     if ((!headerLeft.isEmpty())
1587             || (!headerCenter.isEmpty())
1588             || (!headerRight.isEmpty())) {
1589         xmlWriter.startElement("style:region-left");
1590         xmlWriter.startElement("text:p");
1591         convertPart(sheet, headerLeft, xmlWriter);
1592         xmlWriter.endElement();
1593         xmlWriter.endElement();
1594 
1595         xmlWriter.startElement("style:region-center");
1596         xmlWriter.startElement("text:p");
1597         convertPart(sheet, headerCenter, xmlWriter);
1598         xmlWriter.endElement();
1599         xmlWriter.endElement();
1600 
1601         xmlWriter.startElement("style:region-right");
1602         xmlWriter.startElement("text:p");
1603         convertPart(sheet, headerRight, xmlWriter);
1604         xmlWriter.endElement();
1605         xmlWriter.endElement();
1606     } else {
1607         xmlWriter.startElement("text:p");
1608 
1609         xmlWriter.startElement("text:sheet-name");
1610         xmlWriter.addTextNode("???");
1611         xmlWriter.endElement();
1612 
1613         xmlWriter.endElement();
1614     }
1615     xmlWriter.endElement();
1616 
1617 
1618     xmlWriter.startElement("style:footer");
1619     if ((!footerLeft.isEmpty())
1620             || (!footerCenter.isEmpty())
1621             || (!footerRight.isEmpty())) {
1622         xmlWriter.startElement("style:region-left");
1623         xmlWriter.startElement("text:p");
1624         convertPart(sheet, footerLeft, xmlWriter);
1625         xmlWriter.endElement();
1626         xmlWriter.endElement(); //style:region-left
1627 
1628         xmlWriter.startElement("style:region-center");
1629         xmlWriter.startElement("text:p");
1630         convertPart(sheet, footerCenter, xmlWriter);
1631         xmlWriter.endElement();
1632         xmlWriter.endElement();
1633 
1634         xmlWriter.startElement("style:region-right");
1635         xmlWriter.startElement("text:p");
1636         convertPart(sheet, footerRight, xmlWriter);
1637         xmlWriter.endElement();
1638         xmlWriter.endElement();
1639     } else {
1640         xmlWriter.startElement("text:p");
1641 
1642         xmlWriter.startElement("text:sheet-name");
1643         xmlWriter.addTextNode("Page ");   // ???
1644         xmlWriter.endElement();
1645 
1646         xmlWriter.startElement("text:page-number");
1647         xmlWriter.addTextNode("1");   // ???
1648         xmlWriter.endElement();
1649 
1650         xmlWriter.endElement();
1651     }
1652     xmlWriter.endElement();
1653 }
1654 
1655 inline int compareCellInRow(const Cell &cell1, const Cell &cell2, int maxCols)
1656 {
1657     if (cell1.isNull() != cell2.isNull())
1658         return 0;
1659     if (cell1.isNull())
1660         return 2;
1661     if (maxCols >= 0 && cell1.column() > maxCols)
1662         return 3;
1663     if (cell1.column() != cell2.column())
1664         return 0;
1665     if (!cell1.compareData(cell2))
1666         return 0;
1667     return 1;
1668 }
1669 
1670 inline bool compareCellsInRows(CellStorage *cellStorage, int row1, int row2, int maxCols)
1671 {
1672     Cell cell1 = cellStorage->firstInRow(row1);
1673     Cell cell2 = cellStorage->firstInRow(row2);
1674     while (true) {
1675         int r = compareCellInRow(cell1, cell2, maxCols);
1676         if (r == 0)
1677             return false;
1678         if (r != 1)
1679             break;
1680         cell1 = cellStorage->nextInRow(cell1.column(), cell1.row());
1681         cell2 = cellStorage->nextInRow(cell2.column(), cell2.row());
1682     }
1683     return true;
1684 }
1685 
1686 bool Odf::compareRows(Sheet *sheet, int row1, int row2, int maxCols, OdfSavingContext& tableContext)
1687 {
1688 #if 0
1689     if (!sheet->rowFormats()->rowsAreEqual(row1, row2)) {
1690         return false;
1691     }
1692     if (tableContext.rowHasCellAnchoredShapes(sheet, row1) != tableContext.rowHasCellAnchoredShapes(sheet, row2)) {
1693         return false;
1694     }
1695     return compareCellsInRows(sheet->cellStorage(), row1, row2, maxCols);
1696 #else
1697     Q_UNUSED(maxCols);
1698 
1699     // Optimized comparison by using the RowRepeatStorage to compare the content
1700     // rather then an expensive loop like compareCellsInRows.
1701     int row1repeated = sheet->cellStorage()->rowRepeat(row1);
1702     Q_ASSERT( row2 > row1 );
1703     if (row2 - row1 >= row1repeated) {
1704         return false;
1705     }
1706 
1707     // The RowRepeatStorage does not take to-cell anchored shapes into account
1708     // so we need to check for them explicit.
1709     if (tableContext.rowHasCellAnchoredShapes(sheet, row1) != tableContext.rowHasCellAnchoredShapes(sheet, row2)) {
1710         return false;
1711     }
1712 
1713     // Some sanity-checks to be sure our RowRepeatStorage works as expected.
1714     Q_ASSERT_X( sheet->rowFormats()->rowsAreEqual(row1, row2), __FUNCTION__, QString("Bug in RowRepeatStorage").toLocal8Bit() );
1715     //Q_ASSERT_X( compareCellsInRows(sheet->cellStorage(), row1, row2, maxCols), __FUNCTION__, QString("Bug in RowRepeatStorage").toLocal8Bit() );
1716     Q_ASSERT_X( compareCellInRow(sheet->cellStorage()->lastInRow(row1), sheet->cellStorage()->lastInRow(row2), -1), __FUNCTION__, QString("Bug in RowRepeatStorage").toLocal8Bit() );
1717 
1718     // If we reached that point then the both rows are equal.
1719     return true;
1720 #endif
1721 }
1722 
1723 
1724 
1725 
1726 
1727 
1728 // *************** Settings *****************
1729 
1730 void Odf::loadSheetSettings(Sheet *sheet, const KoOasisSettings::NamedMap &settings)
1731 {
1732     // Find the entry in the map that applies to this sheet (by name)
1733     KoOasisSettings::Items items = settings.entry(sheet->sheetName());
1734     if (items.isNull())
1735         return;
1736     sheet->setHideZero(!items.parseConfigItemBool("ShowZeroValues"));
1737     sheet->setShowGrid(items.parseConfigItemBool("ShowGrid"));
1738     sheet->setFirstLetterUpper(items.parseConfigItemBool("FirstLetterUpper"));
1739 
1740     int cursorX = qMin(KS_colMax, qMax(1, items.parseConfigItemInt("CursorPositionX") + 1));
1741     int cursorY = qMin(KS_rowMax, qMax(1, items.parseConfigItemInt("CursorPositionY") + 1));
1742     sheet->map()->loadingInfo()->setCursorPosition(sheet, QPoint(cursorX, cursorY));
1743 
1744     double offsetX = items.parseConfigItemDouble("xOffset");
1745     double offsetY = items.parseConfigItemDouble("yOffset");
1746     sheet->map()->loadingInfo()->setScrollingOffset(sheet, QPointF(offsetX, offsetY));
1747 
1748     sheet->setShowFormulaIndicator(items.parseConfigItemBool("ShowFormulaIndicator"));
1749     sheet->setShowCommentIndicator(items.parseConfigItemBool("ShowCommentIndicator"));
1750     sheet->setShowPageOutline(items.parseConfigItemBool("ShowPageOutline"));
1751     sheet->setLcMode(items.parseConfigItemBool("lcmode"));
1752     sheet->setAutoCalculationEnabled(items.parseConfigItemBool("autoCalc"));
1753     sheet->setShowColumnNumber(items.parseConfigItemBool("ShowColumnNumber"));
1754 }
1755 
1756 void Odf::saveSheetSettings(Sheet *sheet, KoXmlWriter &settingsWriter)
1757 {
1758     //not into each page into oo spec
1759     settingsWriter.addConfigItem("ShowZeroValues", !sheet->getHideZero());
1760     settingsWriter.addConfigItem("ShowGrid", sheet->getShowGrid());
1761     //not define into oo spec
1762     settingsWriter.addConfigItem("FirstLetterUpper", sheet->getFirstLetterUpper());
1763     settingsWriter.addConfigItem("ShowFormulaIndicator", sheet->getShowFormulaIndicator());
1764     settingsWriter.addConfigItem("ShowCommentIndicator", sheet->getShowCommentIndicator());
1765     settingsWriter.addConfigItem("ShowPageOutline", sheet->isShowPageOutline());
1766     settingsWriter.addConfigItem("lcmode", sheet->getLcMode());
1767     settingsWriter.addConfigItem("autoCalc", sheet->isAutoCalculationEnabled());
1768     settingsWriter.addConfigItem("ShowColumnNumber", sheet->getShowColumnNumber());
1769 }
1770 
1771 QString Odf::savePageLayout(PrintSettings *settings, KoGenStyles &mainStyles, bool formulas, bool zeros)
1772 {
1773     // Create a page layout style.
1774     // 15.2.1 Page Size
1775     // 15.2.4 Print Orientation
1776     // 15.2.5 Margins
1777     KoGenStyle pageLayout = settings->pageLayout().saveOdf();
1778 
1779     // 15.2.13 Print
1780     QString printParameter;
1781     if (settings->printHeaders()) {
1782         printParameter = "headers ";
1783     }
1784     if (settings->printGrid()) {
1785         printParameter += "grid ";
1786     }
1787     /*    if (settings->printComments()) {
1788             printParameter += "annotations ";
1789         }*/
1790     if (settings->printObjects()) {
1791         printParameter += "objects ";
1792     }
1793     if (settings->printCharts()) {
1794         printParameter += "charts ";
1795     }
1796     /*    if (settings->printDrawings()) {
1797             printParameter += "drawings ";
1798         }*/
1799     if (formulas) {
1800         printParameter += "formulas ";
1801     }
1802     if (zeros) {
1803         printParameter += "zero-values ";
1804     }
1805     if (!printParameter.isEmpty()) {
1806         printParameter += "drawings"; //default print style attributes in OO
1807         pageLayout.addProperty("style:print", printParameter);
1808     }
1809 
1810     // 15.2.14 Print Page Order
1811     const QString pageOrder = (settings->pageOrder() == PrintSettings::LeftToRight) ? "ltr" : "ttb";
1812     pageLayout.addProperty("style:print-page-order", pageOrder);
1813 
1814     // 15.2.16 Scale
1815     // FIXME handle cases where only one direction is limited
1816     if (settings->pageLimits().width() > 0 && settings->pageLimits().height() > 0) {
1817         const int pages = settings->pageLimits().width() * settings->pageLimits().height();
1818         pageLayout.addProperty("style:scale-to-pages", pages);
1819     } else if (settings->zoom() != 1.0) {
1820         pageLayout.addProperty("style:scale-to", qRound(settings->zoom() * 100)); // in %
1821     }
1822 
1823     // 15.2.17 Table Centering
1824     if (settings->centerHorizontally() && settings->centerVertically()) {
1825         pageLayout.addProperty("style:table-centering", "both");
1826     } else if (settings->centerHorizontally()) {
1827         pageLayout.addProperty("style:table-centering", "horizontal");
1828     } else if (settings->centerVertically()) {
1829         pageLayout.addProperty("style:table-centering", "vertical");
1830     } else {
1831         pageLayout.addProperty("style:table-centering", "none");
1832     }
1833 
1834     // this is called from Sheet::saveOdfSheetStyleName for writing the SytleMaster so
1835     // the style has to be in the styles.xml file and only there
1836     pageLayout.setAutoStyleInStylesDotXml(true);
1837 
1838     return mainStyles.insert(pageLayout, "pm");
1839 }
1840 
1841 
1842 
1843 
1844 
1845 }  // Sheets
1846 }  // Calligra
1847