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

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 <KoGenStyles.h>
0038 #include <KoParagraphStyle.h>
0039 #include <KoShape.h>
0040 #include <KoShapeRegistry.h>
0041 #include <KoStyleManager.h>
0042 #include <KoTextDocument.h>
0043 #include <KoTextLoader.h>
0044 #include <KoTextSharedLoadingData.h>
0045 #include <KoTextWriter.h>
0046 #include <KoUnit.h>
0047 #include <KoXmlNS.h>
0048 #include <KoXmlWriter.h>
0049 
0050 #include "Cell.h"
0051 #include "CellStorage.h"
0052 #include "Condition.h"
0053 #include "Map.h"
0054 #include "RowColumnFormat.h"
0055 #include "RowFormatStorage.h"
0056 #include "Sheet.h"
0057 #include "Style.h"
0058 #include "StyleManager.h"
0059 #include "Util.h"
0060 #include "Validity.h"
0061 #include "Value.h"
0062 #include "ValueConverter.h"
0063 #include "ValueFormatter.h"
0064 #include "GenValidationStyle.h"
0065 #include "ShapeApplicationData.h"
0066 
0067 #include <float.h>
0068 
0069 // This file contains functionality to load/save a Cell
0070 
0071 namespace Calligra {
0072 namespace Sheets {
0073 
0074 
0075 namespace Odf {
0076 
0077     // cell loading - helper functions
0078     void loadCellText(Cell *cell, const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName);
0079     QString loadCellTextNodes(Cell *cell, const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace);
0080     void loadObjects(Cell *cell, const KoXmlElement &parent, OdfLoadingContext& tableContext, QList<ShapeLoadingData>& shapeData);
0081     ShapeLoadingData loadObject(Cell *cell, const KoXmlElement &element, KoShapeLoadingContext &shapeContext);
0082 
0083     // cell saving - helper functions
0084     QString saveCellStyle(Cell *cell, KoGenStyle &currentCellStyle, KoGenStyles &mainStyles);
0085     void saveCellAnnotation(Cell *cell, KoXmlWriter &xmlwriter);
0086     void saveCellValue(Cell *cell, KoXmlWriter &xmlWriter);
0087 }
0088 
0089 // *************** Loading *****************
0090 bool Odf::loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& tableContext,
0091             const Styles& autoStyles, const QString& cellStyleName,
0092             QList<ShapeLoadingData>& shapeData)
0093 {
0094     static const QString sFormula           = QString::fromLatin1("formula");
0095     static const QString sValidationName    = QString::fromLatin1("validation-name");
0096     static const QString sValueType         = QString::fromLatin1("value-type");
0097     static const QString sBoolean           = QString::fromLatin1("boolean");
0098     static const QString sBooleanValue      = QString::fromLatin1("boolean-value");
0099     static const QString sTrue              = QString::fromLatin1("true");
0100     static const QString sFalse             = QString::fromLatin1("false");
0101     static const QString sFloat             = QString::fromLatin1("float");
0102     static const QString sValue             = QString::fromLatin1("value");
0103     static const QString sCurrency          = QString::fromLatin1("currency");
0104     static const QString sPercentage        = QString::fromLatin1("percentage");
0105     static const QString sDate              = QString::fromLatin1("date");
0106     static const QString sDateValue         = QString::fromLatin1("date-value");
0107     static const QString sTime              = QString::fromLatin1("time");
0108     static const QString sTimeValue         = QString::fromLatin1("time-value");
0109     static const QString sString            = QString::fromLatin1("string");
0110     static const QString sStringValue       = QString::fromLatin1("string-value");
0111     static const QString sNumberColumnsSpanned = QString::fromLatin1("number-columns-spanned");
0112     static const QString sNumberRowsSpanned = QString::fromLatin1("number-rows-spanned");
0113     static const QString sAnnotation        = QString::fromLatin1("annotation");
0114     static const QString sP                 = QString::fromLatin1("p");
0115 
0116     static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" << "of:" << "msoxl:";
0117 
0118     //Search and load each paragraph of text. Each paragraph is separated by a line break.
0119     loadCellText(cell, element, tableContext, autoStyles, cellStyleName);
0120 
0121     //
0122     // formula
0123     //
0124     bool isFormula = false;
0125     if (element.hasAttributeNS(KoXmlNS::table, sFormula)) {
0126         isFormula = true;
0127         QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, QString()));
0128         // debugSheetsODF << "cell:" << cell->name() << "formula :" << oasisFormula;
0129         // each spreadsheet application likes to safe formulas with a different namespace
0130         // prefix, so remove all of them
0131         QString namespacePrefix;
0132         foreach(const QString &prefix, formulaNSPrefixes) {
0133             if (oasisFormula.startsWith(prefix)) {
0134                 oasisFormula.remove(0, prefix.length());
0135                 namespacePrefix = prefix;
0136                 break;
0137             }
0138         }
0139         oasisFormula = Odf::decodeFormula(oasisFormula, cell->locale(), namespacePrefix);
0140         cell->setUserInput(oasisFormula);
0141     } else if (!cell->userInput().isEmpty() && cell->userInput().at(0) == '=')  //prepend ' to the text to avoid = to be painted
0142         cell->setUserInput(cell->userInput().prepend('\''));
0143 
0144     //
0145     // validation
0146     //
0147     if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) {
0148         const QString validationName = element.attributeNS(KoXmlNS::table, sValidationName, QString());
0149         debugSheetsODF << "cell:" << cell->name() << sValidationName << validationName;
0150         Validity validity;
0151         loadValidation(&validity, cell, validationName, tableContext);
0152         if (!validity.isEmpty())
0153             cell->setValidity(validity);
0154     }
0155 
0156     //
0157     // value type
0158     //
0159     if (element.hasAttributeNS(KoXmlNS::office, sValueType)) {
0160         const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, QString());
0161         // debugSheetsODF << "cell:" << cell->name() << "value-type:" << valuetype;
0162         if (valuetype == sBoolean) {
0163             const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, QString()).toLower();
0164             if ((val == sTrue) || (val == sFalse))
0165                 cell->setValue(Value(val == sTrue));
0166         }
0167 
0168         // integer and floating-point value
0169         else if (valuetype == sFloat) {
0170             bool ok = false;
0171             Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok));
0172             if (ok) {
0173                 value.setFormat(Value::fmt_Number);
0174                 cell->setValue(value);
0175 #if 0
0176                 Style style;
0177                 style.setFormatType(Format::Number);
0178                 cell->setStyle(style);
0179 #endif
0180             }
0181             // always set the userInput to the actual value read from the cell, and not whatever happens to be set as text, as the textual representation of a value may be less accurate than the value itself
0182             if (!isFormula)
0183                 cell->setUserInput(cell->sheet()->map()->converter()->asString(value).asString());
0184         }
0185 
0186         // currency value
0187         else if (valuetype == sCurrency) {
0188             bool ok = false;
0189             Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok));
0190             if (ok) {
0191                 value.setFormat(Value::fmt_Money);
0192                 cell->setValue(value);
0193 
0194                 Currency currency;
0195                 if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) {
0196                     currency = Currency(element.attributeNS(KoXmlNS::office, sCurrency, QString()));
0197                 }
0198                 /* TODO: somehow make this work again, all setStyle calls here will be overwritten by cell styles later
0199                 if( style.isEmpty() ) {
0200                     Style style;
0201                     style.setCurrency(currency);
0202                     setStyle(style);
0203                 } */
0204             }
0205         } else if (valuetype == sPercentage) {
0206             bool ok = false;
0207             Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok));
0208             if (ok) {
0209                 value.setFormat(Value::fmt_Percent);
0210                 cell->setValue(value);
0211                 if (!isFormula && cell->userInput().isEmpty())
0212                     cell->setUserInput(cell->sheet()->map()->converter()->asString(value).asString());
0213 // FIXME Stefan: Should be handled by Value::Format. Verify and remove!
0214 #if 0
0215                 Style style;
0216                 style.setFormatType(Format::Percentage);
0217                 setStyle(style);
0218 #endif
0219             }
0220         } else if (valuetype == sDate) {
0221             QString value = element.attributeNS(KoXmlNS::office, sDateValue, QString());
0222 
0223             // "1980-10-15" or "2001-01-01T19:27:41"
0224             int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0;
0225             bool hasTime = false;
0226             bool ok = false;
0227 
0228             int p1 = value.indexOf('-');
0229             if (p1 > 0) {
0230                 year  = value.left(p1).toInt(&ok);
0231                 if (ok) {
0232                     int p2 = value.indexOf('-', ++p1);
0233                     month = value.mid(p1, p2 - p1).toInt(&ok);
0234                     if (ok) {
0235                         // the date can optionally have a time attached
0236                         int p3 = value.indexOf('T', ++p2);
0237                         if (p3 > 0) {
0238                             hasTime = true;
0239                             day = value.mid(p2, p3 - p2).toInt(&ok);
0240                             if (ok) {
0241                                 int p4 = value.indexOf(':', ++p3);
0242                                 hours = value.mid(p3, p4 - p3).toInt(&ok);
0243                                 if (ok) {
0244                                     int p5 = value.indexOf(':', ++p4);
0245                                     minutes = value.mid(p4, p5 - p4).toInt(&ok);
0246                                     if (ok)
0247                                         seconds = value.right(value.length() - p5 - 1).toInt(&ok);
0248                                 }
0249                             }
0250                         } else {
0251                             day = value.right(value.length() - p2).toInt(&ok);
0252                         }
0253                     }
0254                 }
0255             }
0256 
0257             if (ok) {
0258                 if (hasTime)
0259                     cell->setValue(Value(QDateTime(QDate(year, month, day), QTime(hours, minutes, seconds)), cell->sheet()->map()->calculationSettings()));
0260                 else
0261                     cell->setValue(Value(QDate(year, month, day), cell->sheet()->map()->calculationSettings()));
0262 // FIXME Stefan: Should be handled by Value::Format. Verify and remove!
0263 //Sebsauer: Fixed now. Value::Format handles it correct.
0264 #if 0
0265                 Style s;
0266                 s.setFormatType(Format::ShortDate);
0267                 setStyle(s);
0268 #endif
0269                 // debugSheetsODF << "cell:" << cell->name() << "Type: date, value:" << value << "Date:" << year << " -" << month << " -" << day;
0270             }
0271         } else if (valuetype == sTime) {
0272             QString value = element.attributeNS(KoXmlNS::office, sTimeValue, QString());
0273 
0274             // "PT15H10M12S"
0275             int hours = 0, minutes = 0, seconds = 0;
0276             int l = value.length();
0277             QString num;
0278             bool ok = false;
0279             for (int i = 0; i < l; ++i) {
0280                 if (value[i].isNumber()) {
0281                     num += value[i];
0282                     continue;
0283                 } else if (value[i] == 'H')
0284                     hours   = num.toInt(&ok);
0285                 else if (value[i] == 'M')
0286                     minutes = num.toInt(&ok);
0287                 else if (value[i] == 'S')
0288                     seconds = num.toInt(&ok);
0289                 else
0290                     continue;
0291                 //debugSheetsODF << "Num:" << num;
0292                 num.clear();
0293                 if (!ok)
0294                     break;
0295             }
0296 
0297             if (ok) {
0298                 // Value kval( timeToNum( hours, minutes, seconds ) );
0299                 // cell->setValue( kval );
0300                 cell->setValue(Value(QTime(hours % 24, minutes, seconds)));
0301 // FIXME Stefan: Should be handled by Value::Format. Verify and remove!
0302 #if 0
0303                 Style style;
0304                 style.setFormatType(Format::Time);
0305                 setStyle(style);
0306 #endif
0307                 // debugSheetsODF << "cell:" << cell->name() << "Type: time:" << value << "Hours:" << hours << "," << minutes << "," << seconds;
0308             }
0309         } else if (valuetype == sString) {
0310             if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) {
0311                 QString value = element.attributeNS(KoXmlNS::office, sStringValue, QString());
0312                 cell->setValue(Value(value));
0313             } else {
0314                 // use the paragraph(s) read in before
0315                 cell->setValue(Value(cell->userInput()));
0316             }
0317 // FIXME Stefan: Should be handled by Value::Format. Verify and remove!
0318 #if 0
0319             Style style;
0320             style.setFormatType(Format::Text);
0321             setStyle(style);
0322 #endif
0323         } else {
0324             // debugSheetsODF << "cell:" << cell->name() << "  Unknown type. Parsing user input.";
0325             // Set the value by parsing the user input.
0326             cell->parseUserInput(cell->userInput());
0327         }
0328     } else { // no value-type attribute
0329         // debugSheetsODF << "cell:" << cell->name() << "  No value type specified. Parsing user input.";
0330         // Set the value by parsing the user input.
0331         cell->parseUserInput(cell->userInput());
0332     }
0333 
0334     //
0335     // merged cells ?
0336     //
0337     int colSpan = 1;
0338     int rowSpan = 1;
0339     if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) {
0340         bool ok = false;
0341         int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, QString()).toInt(&ok);
0342         if (ok) colSpan = span;
0343     }
0344     if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) {
0345         bool ok = false;
0346         int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, QString()).toInt(&ok);
0347         if (ok) rowSpan = span;
0348     }
0349     if (colSpan > 1 || rowSpan > 1)
0350         cell->mergeCells(cell->column(), cell->row(), colSpan - 1, rowSpan - 1);
0351 
0352     //
0353     // cell comment/annotation
0354     //
0355     KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, sAnnotation);
0356     if (!annotationElement.isNull()) {
0357         QString comment;
0358         KoXmlNode node = annotationElement.firstChild();
0359         while (!node.isNull()) {
0360             KoXmlElement commentElement = node.toElement();
0361             if (!commentElement.isNull())
0362                 if (commentElement.localName() == sP && commentElement.namespaceURI() == KoXmlNS::text) {
0363                     if (!comment.isEmpty()) comment.append('\n');
0364                     comment.append(commentElement.text());
0365                 }
0366 
0367             node = node.nextSibling();
0368         }
0369         if (!comment.isEmpty())
0370             cell->setComment(comment);
0371     }
0372 
0373     loadObjects(cell, element, tableContext, shapeData);
0374 
0375     return true;
0376 }
0377 
0378 bool Odf::saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext)
0379 {
0380     KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter();
0381     KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
0382 
0383     int row = cell->row();
0384     int column = cell->column();
0385 
0386     // see: OpenDocument, 8.1.3 Table Cell
0387     if (!cell->isPartOfMerged())
0388         xmlwriter.startElement("table:table-cell");
0389     else
0390         xmlwriter.startElement("table:covered-table-cell");
0391 #if 0
0392     //add font style
0393     QFont font;
0394     Value const value(cell.value());
0395     if (!cell.isDefault()) {
0396         font = cell.format()->textFont(i, row);
0397         m_styles.addFont(font);
0398 
0399         if (cell.format()->hasProperty(Style::SComment))
0400             hasComment = true;
0401     }
0402 #endif
0403     // NOTE save the value before the style as long as the Formatter does not work correctly
0404     if (cell->link().isEmpty())
0405         saveCellValue(cell, xmlwriter);
0406 
0407     const Style cellStyle = cell->style();
0408 
0409     // Either there's no column and row default and the style's not the default style,
0410     // or the style is different to one of them. The row default takes precedence.
0411     if ((!tableContext.rowDefaultStyles.contains(row) &&
0412             !tableContext.columnDefaultStyles.contains(column) &&
0413             !(cellStyle.isDefault() && cell->conditions().isEmpty())) ||
0414             (tableContext.rowDefaultStyles.contains(row) && tableContext.rowDefaultStyles[row] != cellStyle) ||
0415             (tableContext.columnDefaultStyles.contains(column) && tableContext.columnDefaultStyles[column] != cellStyle)) {
0416         KoGenStyle currentCellStyle; // the type determined in saveCellStyle
0417         QString styleName = saveCellStyle(cell, currentCellStyle, mainStyles);
0418         // skip 'table:style-name' attribute for the default style
0419         if (!currentCellStyle.isDefaultStyle()) {
0420             if (!styleName.isEmpty())
0421                 xmlwriter.addAttribute("table:style-name", styleName);
0422         }
0423     }
0424 
0425     // group empty cells with the same style
0426     const QString comment = cell->comment();
0427     if (cell->isEmpty() && comment.isEmpty() && !cell->isPartOfMerged() && !cell->doesMergeCells() &&
0428             !tableContext.cellHasAnchoredShapes(cell->sheet(), row, column)) {
0429         bool refCellIsDefault = cell->isDefault();
0430         int j = column + 1;
0431         Cell nextCell = cell->sheet()->cellStorage()->nextInRow(column, row);
0432         while (!nextCell.isNull()) {
0433             // if
0434             //   the next cell is not the adjacent one
0435             // or
0436             //   the next cell is not empty
0437             if (nextCell.column() != j || (!nextCell.isEmpty() || tableContext.cellHasAnchoredShapes(cell->sheet(), row, column))) {
0438                 if (refCellIsDefault) {
0439                     // if the origin cell was a default cell,
0440                     // we count the default cells
0441                     repeated = nextCell.column() - j + 1;
0442 
0443                     // check if any of the empty/default cells we skipped contained anchored shapes
0444                     int shapeColumn = tableContext.nextAnchoredShape(cell->sheet(), row, column);
0445                     if (shapeColumn) {
0446                         repeated = qMin(repeated, shapeColumn - column);
0447                     }
0448                 }
0449                 // otherwise we just stop here to process the adjacent
0450                 // cell in the next iteration of the outer loop
0451                 // (in saveCells)
0452                 break;
0453             }
0454 
0455             if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() ||
0456                     !nextCell.comment().isEmpty() || tableContext.cellHasAnchoredShapes(cell->sheet(), row, nextCell.column()) ||
0457                     !(nextCell.style() == cellStyle && nextCell.conditions() == cell->conditions())) {
0458                 break;
0459             }
0460             ++repeated;
0461             // get the next cell and set the index to the adjacent cell
0462             nextCell = cell->sheet()->cellStorage()->nextInRow(j++, row);
0463         }
0464         //debugSheetsODF << "Odf::saveCell: empty cell in column" << column
0465         //<< "repeated" << repeated << "time(s)" << endl;
0466 
0467         if (repeated > 1)
0468             xmlwriter.addAttribute("table:number-columns-repeated", QString::number(repeated));
0469     }
0470 
0471     Validity validity = cell->validity();
0472     if (!validity.isEmpty()) {
0473         GenValidationStyle styleVal(&validity, cell->sheet()->map()->converter());
0474         xmlwriter.addAttribute("table:validation-name", tableContext.valStyle.insert(styleVal));
0475     }
0476     if (cell->isFormula()) {
0477         //debugSheetsODF <<"Formula found";
0478         QString formula = Odf::encodeFormula(cell->userInput(), cell->locale());
0479         xmlwriter.addAttribute("table:formula", formula);
0480     }
0481 
0482     if (cell->doesMergeCells()) {
0483         int colSpan = cell->mergedXCells() + 1;
0484         int rowSpan = cell->mergedYCells() + 1;
0485 
0486         if (colSpan > 1)
0487             xmlwriter.addAttribute("table:number-columns-spanned", QString::number(colSpan));
0488 
0489         if (rowSpan > 1)
0490             xmlwriter.addAttribute("table:number-rows-spanned", QString::number(rowSpan));
0491     }
0492 
0493     saveCellAnnotation(cell, xmlwriter);
0494 
0495     if (!cell->isFormula() && !cell->link().isEmpty()) {
0496         //debugSheetsODF<<"Link found";
0497         xmlwriter.startElement("text:p");
0498         xmlwriter.startElement("text:a");
0499         const QString url = cell->link();
0500         //Reference cell is started by '#'
0501         if (Util::localReferenceAnchor(url))
0502             xmlwriter.addAttribute("xlink:href", ('#' + url));
0503         else
0504             xmlwriter.addAttribute("xlink:href", url);
0505         xmlwriter.addAttribute("xlink:type", "simple");
0506         xmlwriter.addTextNode(cell->userInput());
0507         xmlwriter.endElement();
0508         xmlwriter.endElement();
0509     }
0510 
0511     if (!cell->isEmpty() && cell->link().isEmpty()) {
0512         QSharedPointer<QTextDocument> doc = cell->richText();
0513         if (doc) {
0514             QTextCharFormat format = cell->style().asCharFormat();
0515             ((KoCharacterStyle *)cell->sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
0516 
0517             KoTextWriter writer(tableContext.shapeContext);
0518 
0519             writer.write(doc.data(), 0);
0520         } else {
0521             xmlwriter.startElement("text:p");
0522             xmlwriter.addTextNode(cell->displayText().toUtf8());
0523             xmlwriter.endElement();
0524         }
0525     }
0526 
0527     // flake
0528     // Save shapes that are anchored to this cell.
0529     // see: OpenDocument, 2.3.1 Text Documents
0530     // see: OpenDocument, 9.2 Drawing Shapes
0531     if (tableContext.cellHasAnchoredShapes(cell->sheet(), row, column)) {
0532         const QList<KoShape*> shapes = tableContext.cellAnchoredShapes(cell->sheet(), row, column);
0533         for (int i = 0; i < shapes.count(); ++i) {
0534             KoShape* const shape = shapes[i];
0535             const QPointF bottomRight = shape->boundingRect().bottomRight();
0536             qreal endX = 0.0;
0537             qreal endY = 0.0;
0538             const int scol = cell->sheet()->leftColumn(bottomRight.x(), endX);
0539             const int srow = cell->sheet()->topRow(bottomRight.y(), endY);
0540             qreal offsetX = cell->sheet()->columnPosition(column);
0541             qreal offsetY = cell->sheet()->rowPosition(row);
0542             tableContext.shapeContext.addShapeOffset(shape, QTransform::fromTranslate(-offsetX, -offsetY));
0543             shape->setAdditionalAttribute("table:end-cell-address", saveRegion(Region(QPoint(scol, srow)).name()));
0544             shape->setAdditionalAttribute("table:end-x", QString::number(bottomRight.x() - endX) + "pt");
0545             shape->setAdditionalAttribute("table:end-y", QString::number(bottomRight.y() - endY) + "pt");
0546             shape->saveOdf(tableContext.shapeContext);
0547             shape->removeAdditionalAttribute("table:end-cell-address");
0548             shape->removeAdditionalAttribute("table:end-x");
0549             shape->removeAdditionalAttribute("table:end-y");
0550             tableContext.shapeContext.removeShapeOffset(shape);
0551         }
0552     }
0553 
0554     xmlwriter.endElement();
0555     return true;
0556 }
0557 
0558 
0559 // loading - helper functions
0560 
0561 QString Odf::loadCellTextNodes(Cell *cell, const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace)
0562 {
0563     QString cellText;
0564     bool countedOwnFragments = false;
0565     bool prevWasText = false;
0566     for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0567         if (n.isText()) {
0568             prevWasText = true;
0569             QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), *stripLeadingSpace);
0570             if (!t.isEmpty()) {
0571                 *stripLeadingSpace = t[t.length() - 1].isSpace();
0572                 cellText += t;
0573                 if (!countedOwnFragments) {
0574                     // We only count the number of different parent elements which have text. That is
0575                     // so cause different parent-elements may mean different styles which means
0576                     // rich-text while the same parent element means the same style so we can easily
0577                     // put them together into one string.
0578                     countedOwnFragments = true;
0579                     ++(*textFragmentCount);
0580                 }
0581             }
0582         } else {
0583             KoXmlElement e = n.toElement();
0584             if (!e.isNull()) {
0585                 if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() - 1].isSpace()) {
0586                     // A trailing space of the cellText collected so far needs to be preserved when
0587                     // more text-nodes within the same parent follow but if an element like e.g.
0588                     // text:s follows then a trailing space needs to be removed.
0589                     cellText.chop(1);
0590                 }
0591                 prevWasText = false;
0592 
0593                 // We can optimize some elements like text:s (space), text:tab (tabulator) and
0594                 // text:line-break (new-line) to not produce rich-text but add the equivalent
0595                 // for them in plain-text.
0596                 const bool isTextNs = e.namespaceURI() == KoXmlNS::text;
0597                 if (isTextNs && e.localName() == "s") {
0598                     const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c", QString()).toInt());
0599                     cellText += QString().fill(32, howmany);
0600                 } else if (isTextNs && e.localName() == "tab") {
0601                     cellText += '\t';
0602                 } else if (isTextNs && e.localName() == "line-break") {
0603                     cellText += '\n';
0604                     ++(*lineCount);
0605                 } else if (isTextNs && e.localName() == "span") {
0606                     // Nested span-elements means recursive evaluation.
0607                     cellText += loadCellTextNodes(cell, e, textFragmentCount, lineCount, hasRichText, stripLeadingSpace);
0608                 } else if (!isTextNs ||
0609                               ( e.localName() != "annotation" &&
0610                                 e.localName() != "bookmark" &&
0611                                 e.localName() != "meta" &&
0612                                 e.localName() != "tag" )) {
0613                     // Seems we have an element we cannot easily translate to a string what
0614                     // means it's all rich-text now.
0615                     *hasRichText = true;
0616                 }
0617             }
0618         }
0619     }
0620     return cellText;
0621 }
0622 
0623 // recursively goes through all children of parent and returns true if there is any element
0624 // in the draw: namespace in this subtree
0625 static bool findDrawElements(const KoXmlElement& parent)
0626 {
0627     KoXmlElement element;
0628     forEachElement(element , parent) {
0629         if (element.namespaceURI() == KoXmlNS::draw)
0630             return true;
0631         if (findDrawElements(element))
0632             return true;
0633     }
0634     return false;
0635 }
0636 
0637 // Similar to KoXml::namedItemNS except that children of span tags will be evaluated too.
0638 static KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString &nsURI, const QString &localName)
0639 {
0640     KoXmlNode n = node.firstChild();
0641     for (; !n.isNull(); n = n.nextSibling()) {
0642         if (n.isElement()) {
0643             if (n.localName() == localName && n.namespaceURI() == nsURI) {
0644                 return n.toElement();
0645             }
0646             if (n.localName() == "span" && n.namespaceURI() == nsURI) {
0647                 KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not recursive
0648                 if (!e.isNull()) {
0649                     return e;
0650                 }
0651             }
0652         }
0653     }
0654     return KoXmlElement();
0655 }
0656 
0657 void Odf::loadCellText(Cell *cell, const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName)
0658 {
0659     //Search and load each paragraph of text. Each paragraph is separated by a line break
0660     KoXmlElement textParagraphElement;
0661     QString cellText;
0662 
0663     int lineCount = 0;
0664     bool hasRichText = false;
0665     bool stripLeadingSpace = true;
0666 
0667     forEachElement(textParagraphElement , parent) {
0668         if (textParagraphElement.localName() == "p" &&
0669                 textParagraphElement.namespaceURI() == KoXmlNS::text) {
0670 
0671             // the text:a link could be located within a text:span element
0672             KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, KoXmlNS::text, "a");
0673             if (!textA.isNull() && textA.hasAttributeNS(KoXmlNS::xlink, "href")) {
0674                 QString link = textA.attributeNS(KoXmlNS::xlink, "href", QString());
0675                 cellText = textA.text();
0676                 cell->setUserInput(cellText);
0677                 hasRichText = false;
0678                 lineCount = 0;
0679                 // The value will be set later in loadOdf().
0680                 if ((!link.isEmpty()) && (link[0] == '#'))
0681                     link.remove(0, 1);
0682                 cell->setLink(link);
0683                 // Abort here cause we can handle only either a link in a cell or (rich-)text but not both.
0684                 break;
0685             }
0686 
0687             if (!cellText.isNull())
0688                 cellText += '\n';
0689 
0690             ++lineCount;
0691             int textFragmentCount = 0;
0692 
0693             // Our text could contain formatting for value or result of formula or a mix of
0694             // multiple text:span elements with text-nodes and line-break's.
0695             cellText += loadCellTextNodes(cell, textParagraphElement, &textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace);
0696 
0697             // If we got text from multiple different sources (e.g. from the text:p and a
0698             // child text:span) then we have very likely rich-text.
0699             if (!hasRichText)
0700                 hasRichText = textFragmentCount >= 2;
0701         }
0702     }
0703 
0704     if (!cellText.isNull()) {
0705         if (hasRichText && !findDrawElements(parent)) {
0706             // for now we don't support richtext and embedded shapes in the same cell;
0707             // this is because they would currently be loaded twice, once by the KoTextLoader
0708             // and later properly by the cell itself
0709 
0710             Style style; style.setDefault();
0711             if (!cellStyleName.isEmpty()) {
0712                 if (autoStyles.contains(cellStyleName))
0713                     style.merge(autoStyles[cellStyleName]);
0714                 else {
0715                     const CustomStyle* namedStyle = cell->sheet()->map()->styleManager()->style(cellStyleName);
0716                     if (namedStyle)
0717                         style.merge(*namedStyle);
0718                 }
0719             }
0720 
0721             QTextCharFormat format = style.asCharFormat();
0722             ((KoCharacterStyle *)cell->sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
0723 
0724             QSharedPointer<QTextDocument> doc(new QTextDocument);
0725             KoTextDocument(doc.data()).setStyleManager(cell->sheet()->map()->textStyleManager());
0726 
0727             Q_ASSERT(tableContext.shapeContext);
0728             KoTextLoader loader(*tableContext.shapeContext);
0729             QTextCursor cursor(doc.data());
0730             loader.loadBody(parent, cursor);
0731 
0732             cell->setUserInput(doc->toPlainText());
0733             cell->setRichText(doc);
0734         } else {
0735             cell->setUserInput(cellText);
0736         }
0737     }
0738 
0739     // enable word wrapping if multiple lines of text have been found.
0740     if (lineCount >= 2) {
0741         Style newStyle;
0742         newStyle.setWrapText(true);
0743         cell->setStyle(newStyle);
0744     }
0745 }
0746 
0747 void Odf::loadObjects(Cell *cell, const KoXmlElement &parent, OdfLoadingContext& tableContext, QList<ShapeLoadingData>& shapeData)
0748 {
0749     // Register additional attributes, that identify shapes anchored in cells.
0750     // Their dimensions need adjustment after all rows are loaded,
0751     // because the position of the end cell is not always known yet.
0752     KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
0753                 KoXmlNS::table, "end-cell-address",
0754                 "table:end-cell-address"));
0755     KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
0756                 KoXmlNS::table, "end-x",
0757                 "table:end-x"));
0758     KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
0759                 KoXmlNS::table, "end-y",
0760                 "table:end-y"));
0761 
0762     KoXmlElement element;
0763     forEachElement(element, parent) {
0764         if (element.namespaceURI() != KoXmlNS::draw)
0765             continue;
0766 
0767         if (element.localName() == "a") {
0768             // It may the case that the object(s) are embedded into a hyperlink so actions are done on
0769             // clicking it/them but since we do not supported objects-with-hyperlinks yet we just fetch
0770             // the inner elements and use them to at least create and show the objects (see bug 249862).
0771             KoXmlElement e;
0772             forEachElement(e, element) {
0773                 if (e.namespaceURI() != KoXmlNS::draw)
0774                     continue;
0775                 ShapeLoadingData data = loadObject(cell, e, *tableContext.shapeContext);
0776                 if (data.shape) {
0777                     shapeData.append(data);
0778                 }
0779             }
0780         } else {
0781             ShapeLoadingData data = loadObject(cell, element, *tableContext.shapeContext);
0782             if (data.shape) {
0783                 shapeData.append(data);
0784             }
0785         }
0786     }
0787 }
0788 
0789 Odf::ShapeLoadingData Odf::loadObject(Cell *cell, const KoXmlElement &element, KoShapeLoadingContext &shapeContext)
0790 {
0791     ShapeLoadingData data;
0792     data.shape = 0;
0793     KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext);
0794     if (!shape) {
0795         debugSheetsODF << "Unable to load shape with localName=" << element.localName();
0796         return data;
0797     }
0798 
0799     cell->sheet()->addShape(shape);
0800 
0801     // The position is relative to the upper left sheet corner until now. Move it.
0802     QPointF position = shape->position();
0803     // Remember how far we're off from the top-left corner of this cell
0804     double offsetX = position.x();
0805     double offsetY = position.y();
0806     for (int col = 1; col < cell->column(); ++col)
0807         position += QPointF(cell->sheet()->columnFormat(col)->width(), 0.0);
0808     if (cell->row() > 1)
0809         position += QPointF(0.0, cell->sheet()->rowFormats()->totalRowHeight(1, cell->row() - 1));
0810     shape->setPosition(position);
0811 
0812     dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(true);
0813 
0814     // All three attributes are necessary for cell anchored shapes.
0815     // Otherwise, they are anchored in the sheet.
0816     if (!shape->hasAdditionalAttribute("table:end-cell-address") ||
0817             !shape->hasAdditionalAttribute("table:end-x") ||
0818             !shape->hasAdditionalAttribute("table:end-y")) {
0819         debugSheetsODF << "Not all attributes found, that are necessary for cell anchoring.";
0820         return data;
0821     }
0822 
0823     Region endCell(loadRegion(shape->additionalAttribute("table:end-cell-address")),
0824                    cell->sheet()->map(), cell->sheet());
0825     if (!endCell.isValid() || !endCell.isSingular())
0826         return data;
0827 
0828     QString string = shape->additionalAttribute("table:end-x");
0829     if (string.isNull())
0830         return data;
0831     double endX = KoUnit::parseValue(string);
0832 
0833     string = shape->additionalAttribute("table:end-y");
0834     if (string.isNull())
0835         return data;
0836     double endY = KoUnit::parseValue(string);
0837 
0838     data.shape = shape;
0839     data.startCell = QPoint(cell->column(), cell->row());
0840     data.offset = QPointF(offsetX, offsetY);
0841     data.endCell = endCell;
0842     data.endPoint = QPointF(endX, endY);
0843 
0844     // The column dimensions are already the final ones, but not the row dimensions.
0845     // The default height is used for the not yet loaded rows.
0846     // TODO Stefan: Honor non-default row heights later!
0847     // subtract offset because the accumulated width and height we calculate below starts
0848     // at the top-left corner of this cell, but the shape can have an offset to that corner
0849     QSizeF size = QSizeF(endX - offsetX, endY - offsetY);
0850     for (int col = cell->column(); col < endCell.firstRange().left(); ++col)
0851         size += QSizeF(cell->sheet()->columnFormat(col)->width(), 0.0);
0852     if (endCell.firstRange().top() > cell->row())
0853         size += QSizeF(0.0, cell->sheet()->rowFormats()->totalRowHeight(cell->row(), endCell.firstRange().top() - 1));
0854     shape->setSize(size);
0855 
0856     return data;
0857 }
0858 
0859 
0860 // saving - helper functions
0861 
0862 void Odf::saveCellAnnotation(Cell *cell, KoXmlWriter &xmlwriter)
0863 {
0864     const QString comment = cell->comment();
0865     if (comment.isEmpty()) return;
0866 
0867     //<office:annotation draw:style-name="gr1" draw:text-style-name="P1" svg:width="2.899cm" svg:height="2.691cm" svg:x="2.858cm" svg:y="0.001cm" draw:caption-point-x="-2.858cm" draw:caption-point-y="-0.001cm">
0868     xmlwriter.startElement("office:annotation");
0869     const QStringList text = comment.split('\n', QString::SkipEmptyParts);
0870     for (QStringList::ConstIterator it = text.begin(); it != text.end(); ++it) {
0871         xmlwriter.startElement("text:p");
0872         xmlwriter.addTextNode(*it);
0873         xmlwriter.endElement();
0874     }
0875     xmlwriter.endElement();
0876 }
0877 
0878 
0879 QString Odf::saveCellStyle(Cell *cell, KoGenStyle &currentCellStyle, KoGenStyles &mainStyles)
0880 {
0881     const Conditions conditions = cell->conditions();
0882     if (!conditions.isEmpty()) {
0883         // this has to be an automatic style
0884         currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell");
0885         saveConditions(&conditions, currentCellStyle, cell->sheet()->map()->converter());
0886     }
0887     Style style = cell->style();
0888     return saveStyle(&style, currentCellStyle, mainStyles, cell->sheet()->map()->styleManager());
0889 }
0890 
0891 void Odf::saveCellValue(Cell *cell, KoXmlWriter &xmlWriter)
0892 {
0893     Value value = cell->value();
0894     // Determine the format that we will be storing.
0895     // This is usually the format that is actually shown - doing so mixes style and content, but that's how
0896     // LO does it, so we need to stay compatible
0897     Format::Type shownFormat = cell->style().formatType();
0898     if (shownFormat == Format::Generic)
0899         shownFormat = cell->sheet()->map()->formatter()->determineFormatting(value, shownFormat);
0900     Value::Format saveFormat = Value::fmt_None;
0901     Value::Format valueFormat = value.format();
0902     if (valueFormat == Value::fmt_Boolean)
0903         saveFormat = Value::fmt_Boolean;
0904     else if (valueFormat == Value::fmt_String)  // if it's a text, it needs to be stored as a text
0905         saveFormat = Value::fmt_String;
0906     else if (Format::isDate(shownFormat))
0907         saveFormat = Value::fmt_Date;
0908     else if (Format::isTime(shownFormat))
0909         saveFormat = Value::fmt_Time;
0910     else if (Format::isNumber(shownFormat))
0911         saveFormat = Value::fmt_Number;
0912     else if (Format::isMoney(shownFormat))
0913         saveFormat = Value::fmt_Money;
0914     else if (shownFormat == Format::Percentage)
0915         saveFormat = Value::fmt_Percent;
0916     else if (shownFormat == Format::Text)
0917         saveFormat = Value::fmt_String;
0918     else if (shownFormat == Format::Custom)
0919         saveFormat = valueFormat;
0920 
0921     switch (saveFormat) {
0922     case Value::fmt_None: break;  //NOTHING HERE
0923     case Value::fmt_Boolean: {
0924         xmlWriter.addAttribute("office:value-type", "boolean");
0925         xmlWriter.addAttribute("office:boolean-value", (value.asBoolean() ? "true" : "false"));
0926         break;
0927     }
0928     case Value::fmt_Number: {
0929         xmlWriter.addAttribute("office:value-type", "float");
0930         if (value.isInteger())
0931             xmlWriter.addAttribute("office:value", QString::number(value.asInteger()));
0932         else
0933             xmlWriter.addAttribute("office:value", QString::number(numToDouble(value.asFloat()), 'g', DBL_DIG));
0934         break;
0935     }
0936     case Value::fmt_Percent: {
0937         xmlWriter.addAttribute("office:value-type", "percentage");
0938         xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value.asFloat())));
0939         break;
0940     }
0941     case Value::fmt_Money: {
0942         xmlWriter.addAttribute("office:value-type", "currency");
0943         const Style style = cell->style();
0944         if (style.hasAttribute(Style::CurrencyFormat)) {
0945             Currency currency = style.currency();
0946             xmlWriter.addAttribute("office:currency", currency.code());
0947         }
0948         xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value.asFloat())));
0949         break;
0950     }
0951     case Value::fmt_DateTime: break;  //NOTHING HERE
0952     case Value::fmt_Date: {
0953         xmlWriter.addAttribute("office:value-type", "date");
0954         xmlWriter.addAttribute("office:date-value",
0955                                value.asDate(cell->sheet()->map()->calculationSettings()).toString(Qt::ISODate));
0956         break;
0957     }
0958     case Value::fmt_Time: {
0959         xmlWriter.addAttribute("office:value-type", "time");
0960         xmlWriter.addAttribute("office:time-value",
0961                                value.asTime().toString("'PT'hh'H'mm'M'ss'S'"));
0962         break;
0963     }
0964     case Value::fmt_String: {
0965         xmlWriter.addAttribute("office:value-type", "string");
0966         xmlWriter.addAttribute("office:string-value", value.asString());
0967         break;
0968     }
0969     };
0970 }
0971 
0972 
0973 
0974 
0975 
0976 }  // Sheets
0977 }  // Calligra
0978