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 ¤tCellStyle, 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 ¤tCellStyle, 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