1/* This file is part of the KDE project
2 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
4 Copyright 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
5 Copyright 2004-2005 Tomas Mecir <mecirt@gmail.com>
6 Copyright 2004-2006 Inge Wallin <inge@lysator.liu.se>
7 Copyright 1999-2002,2004,2005 Laurent Montel <montel@kde.org>
8 Copyright 2002-2005 Ariya Hidayat <ariya@kde.org>
9 Copyright 2001-2003 Philipp Mueller <philipp.mueller@gmx.de>
10 Copyright 2002-2003 Norbert Andres <nandres@web.de>
11 Copyright 2003 Reinhart Geiser <geiseri@kde.org>
12 Copyright 2003-2005 Meni Livne <livne@kde.org>
13 Copyright 2003 Peter Simonsson <psn@linux.se>
14 Copyright 1999-2002 David Faure <faure@kde.org>
15 Copyright 2000-2002 Werner Trobin <trobin@kde.org>
16 Copyright 1999,2002 Harri Porten <porten@kde.org>
17 Copyright 2002 John Dailey <dailey@vt.edu>
18 Copyright 1998-2000 Torben Weis <weis@kde.org>
19 Copyright 2000 Bernd Wuebben <wuebben@kde.org>
20 Copyright 2000 Simon Hausmann <hausmann@kde.org
21 Copyright 1999 Stephan Kulow <coolo@kde.org>
22 Copyright 1999 Michael Reiher <michael.reiher@gmx.de>
23 Copyright 1999 Boris Wedl <boris.wedl@kfunigraz.ac.at>
24 Copyright 1998-1999 Reginald Stadlbauer <reggie@kde.org>
25
26 This library is free software; you can redistribute it and/or
27 modify it under the terms of the GNU Library General Public
28 License as published by the Free Software Foundation; either
29 version 2 of the License, or (at your option) any later version.
30
31 This library is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
34 Library General Public License for more details.
35
36 You should have received a copy of the GNU Library General Public License
37 along with this library; see the file COPYING.LIB. If not, write to
38 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
39 Boston, MA 02110-1301, USA.
40*/
41
42// Local
43#include "Cell.h"
44
45#include <stdlib.h>
46#include <ctype.h>
47#include <float.h>
48#include <math.h>
49
50#include "CalculationSettings.h"
51#include "CellStorage.h"
52#include "Condition.h"
53#include "Formula.h"
54#include "GenValidationStyle.h"
55#include "Global.h"
56#include "Localization.h"
57#include "LoadingInfo.h"
58#include "Map.h"
59#include "NamedAreaManager.h"
60#include "OdfLoadingContext.h"
61#include "OdfSavingContext.h"
62#include "RowColumnFormat.h"
63#include "RowFormatStorage.h"
64#include "ShapeApplicationData.h"
65#include "Sheet.h"
66#include "Style.h"
67#include "StyleManager.h"
68#include "Util.h"
69#include "Value.h"
70#include "Validity.h"
71#include "ValueConverter.h"
72#include "ValueFormatter.h"
73#include "ValueParser.h"
74#include "StyleStorage.h"
75
76#include <KoShape.h>
77#include <KoShapeLoadingContext.h>
78#include <KoShapeRegistry.h>
79#include <KoStyleStack.h>
80#include <KoXmlNS.h>
81#include <KoXmlReader.h>
82#include <KoOdfStylesReader.h>
83#include <KoXmlWriter.h>
84
85#include <KoTextLoader.h>
86#include <KoStyleManager.h>
87#include <KoTextSharedLoadingData.h>
88#include <KoTextDocument.h>
89#include <KoTextWriter.h>
90#include <KoParagraphStyle.h>
91
92#include <kdebug.h>
93
94#include <QTimer>
95#include <QTextDocument>
96#include <QTextCursor>
97
98using namespace Calligra::Sheets;
99
100class Cell::Private : public QSharedData
101{
102public:
103 Private() : sheet(0), column(0), row(0) {}
104
105 Sheet* sheet;
106 uint column : 17; // KS_colMax
107 uint row : 21; // KS_rowMax
108};
109
110
111Cell::Cell()
112 : d(0)
113{
114}
115
116Cell::Cell(const Sheet* sheet, int col, int row)
117 : d(new Private)
118{
119 Q_ASSERT(sheet != 0);
120 Q_ASSERT_X(1 <= col && col <= KS_colMax, __FUNCTION__, QString("%1 out of bounds").arg(col).toLocal8Bit());
121 Q_ASSERT_X(1 <= row && row <= KS_rowMax, __FUNCTION__, QString("%1 out of bounds").arg(row).toLocal8Bit());
122 d->sheet = const_cast<Sheet*>(sheet);
123 d->column = col;
124 d->row = row;
125}
126
127Cell::Cell(const Sheet* sheet, const QPoint& pos)
128 : d(new Private)
129{
130 Q_ASSERT(sheet != 0);
131 Q_ASSERT_X(1 <= pos.x() && pos.x() <= KS_colMax, __FUNCTION__, QString("%1 out of bounds").arg(pos.x()).toLocal8Bit());
132 Q_ASSERT_X(1 <= pos.y() && pos.y() <= KS_rowMax, __FUNCTION__, QString("%1 out of bounds").arg(pos.y()).toLocal8Bit());
133 d->sheet = const_cast<Sheet*>(sheet);
134 d->column = pos.x();
135 d->row = pos.y();
136}
137
138Cell::Cell(const Cell& other)
139 : d(other.d)
140{
141}
142
143Cell::~Cell()
144{
145}
146
147// Return the sheet that this cell belongs to.
148Sheet* Cell::sheet() const
149{
150 Q_ASSERT(!isNull());
151 return d->sheet;
152}
153
154KLocale* Cell::locale() const
155{
156 return sheet()->map()->calculationSettings()->locale();
157}
158
159// Return true if this is the default cell.
160bool Cell::isDefault() const
161{
162 // check each stored attribute
163 if (!value().isEmpty())
164 return false;
165 if (formula() != Formula::empty())
166 return false;
167 if (!link().isEmpty())
168 return false;
169 if (doesMergeCells() == true)
170 return false;
171 if (!style().isDefault())
172 return false;
173 if (!comment().isEmpty())
174 return false;
175 if (!conditions().isEmpty())
176 return false;
177 if (!validity().isEmpty())
178 return false;
179 return true;
180}
181
182// Return true if this is the default cell (apart from maybe a custom style).
183bool Cell::hasDefaultContent() const
184{
185 // check each stored attribute
186 if (value() != Value())
187 return false;
188 if (formula() != Formula::empty())
189 return false;
190 if (!link().isEmpty())
191 return false;
192 if (doesMergeCells() == true)
193 return false;
194 if (!comment().isEmpty())
195 return false;
196 if (!conditions().isEmpty())
197 return false;
198 if (!validity().isEmpty())
199 return false;
200 return true;
201}
202
203bool Cell::isEmpty() const
204{
205 // empty = no value or formula
206 if (value() != Value())
207 return false;
208 if (formula() != Formula())
209 return false;
210 return true;
211}
212
213bool Cell::isNull() const
214{
215 return (!d);
216}
217
218// Return true if this cell is a formula.
219//
220bool Cell::isFormula() const
221{
222 return !formula().expression().isEmpty();
223}
224
225// Return the column number of this cell.
226//
227int Cell::column() const
228{
229 // Make sure this isn't called for the null cell. This assert
230 // can save you (could have saved me!) the hassle of some very
231 // obscure bugs.
232 Q_ASSERT(!isNull());
233 Q_ASSERT(1 <= d->column); //&& d->column <= KS_colMax );
234 return d->column;
235}
236
237// Return the row number of this cell.
238int Cell::row() const
239{
240 // Make sure this isn't called for the null cell. This assert
241 // can save you (could have saved me!) the hassle of some very
242 // obscure bugs.
243 Q_ASSERT(!isNull());
244 Q_ASSERT(1 <= d->row); //&& d->row <= KS_rowMax );
245 return d->row;
246}
247
248// Return the name of this cell, i.e. the string that the user would
249// use to reference it. Example: A1, BZ16
250//
251QString Cell::name() const
252{
253 return name(column(), row());
254}
255
256// Return the name of any cell given by (col, row).
257//
258// static
259QString Cell::name(int col, int row)
260{
261 return columnName(col) + QString::number(row);
262}
263
264// Return the name of this cell, including the sheet name.
265// Example: sheet1!A5
266//
267QString Cell::fullName() const
268{
269 return fullName(sheet(), column(), row());
270}
271
272// Return the full name of any cell given a sheet and (col, row).
273//
274// static
275QString Cell::fullName(const Sheet* s, int col, int row)
276{
277 return s->sheetName() + '!' + name(col, row);
278}
279
280// Return the symbolic name of the column of this cell. Examples: A, BB.
281//
282QString Cell::columnName() const
283{
284 return columnName(column());
285}
286
287// Return the symbolic name of any column.
288//
289// static
290QString Cell::columnName(uint column)
291{
292 if (column < 1) //|| column > KS_colMax)
293 return QString("@@@");
294
295 QString str;
296 unsigned digits = 1;
297 unsigned offset = 0;
298
299 --column;
300
301 for (unsigned limit = 26; column >= limit + offset; limit *= 26, ++digits)
302 offset += limit;
303
304 for (unsigned col = column - offset; digits; --digits, col /= 26)
305 str.prepend(QChar('A' + (col % 26)));
306
307 return str;
308}
309
310QString Cell::comment() const
311{
312 return sheet()->cellStorage()->comment(d->column, d->row);
313}
314
315void Cell::setComment(const QString& comment)
316{
317 sheet()->cellStorage()->setComment(Region(cellPosition()), comment);
318}
319
320Conditions Cell::conditions() const
321{
322 return sheet()->cellStorage()->conditions(d->column, d->row);
323}
324
325void Cell::setConditions(const Conditions& conditions)
326{
327 sheet()->cellStorage()->setConditions(Region(cellPosition()), conditions);
328}
329
330Database Cell::database() const
331{
332 return sheet()->cellStorage()->database(d->column, d->row);
333}
334
335Formula Cell::formula() const
336{
337 return sheet()->cellStorage()->formula(d->column, d->row);
338}
339
340void Cell::setFormula(const Formula& formula)
341{
342 sheet()->cellStorage()->setFormula(column(), row(), formula);
343}
344
345Style Cell::style() const
346{
347 return sheet()->cellStorage()->style(d->column, d->row);
348}
349
350Style Cell::effectiveStyle() const
351{
352 Style style = sheet()->cellStorage()->style(d->column, d->row);
353 // use conditional formatting attributes
354 const Style conditionalStyle = conditions().testConditions(*this);
355 if (!conditionalStyle.isEmpty()) {
356 style.merge(conditionalStyle);
357 }
358 return style;
359}
360
361void Cell::setStyle(const Style& style)
362{
363 sheet()->cellStorage()->setStyle(Region(cellPosition()), style);
364 sheet()->cellStorage()->styleStorage()->contains(cellPosition());
365}
366
367Validity Cell::validity() const
368{
369 return sheet()->cellStorage()->validity(d->column, d->row);
370}
371
372void Cell::setValidity(Validity validity)
373{
374 sheet()->cellStorage()->setValidity(Region(cellPosition()), validity);
375}
376
377
378
379
380
381// Return the user input of this cell. This could, for instance, be a
382// formula.
383//
384QString Cell::userInput() const
385{
386 const Formula formula = this->formula();
387 if (!formula.expression().isEmpty())
388 return formula.expression();
389 return sheet()->cellStorage()->userInput(d->column, d->row);
390}
391
392void Cell::setUserInput(const QString& string)
393{
394 QString old = userInput();
395
396 if (!string.isEmpty() && string[0] == '=') {
397 // set the formula
398 Formula formula(sheet(), *this);
399 formula.setExpression(string);
400 setFormula(formula);
401 // remove an existing user input (the non-formula one)
402 sheet()->cellStorage()->setUserInput(d->column, d->row, QString());
403 } else {
404 // remove an existing formula
405 setFormula(Formula::empty());
406 // set the value
407 sheet()->cellStorage()->setUserInput(d->column, d->row, string);
408 }
409
410 if (old != string) {
411 // remove any existing richtext
412 setRichText(QSharedPointer<QTextDocument>());
413 }
414}
415
416void Cell::setRawUserInput(const QString& string)
417{
418 if (!string.isEmpty() && string[0] == '=') {
419 // set the formula
420 Formula formula(sheet(), *this);
421 formula.setExpression(string);
422 setFormula(formula);
423 } else {
424 // set the value
425 sheet()->cellStorage()->setUserInput(d->column, d->row, string);
426 }
427}
428
429
430// Return the out text, i.e. the text that is visible in the cells
431// square when shown. This could, for instance, be the calculated
432// result of a formula.
433//
434QString Cell::displayText(const Style& s, Value *v, bool *showFormula) const
435{
436 if (isNull())
437 return QString();
438
439 QString string;
440 const Style style = s.isEmpty() ? effectiveStyle() : s;
441 // Display a formula if warranted. If not, display the value instead;
442 // this is the most common case.
443 if ( isFormula() && !(sheet()->isProtected() && style.hideFormula()) &&
444 ( (showFormula && *showFormula) || (!showFormula && sheet()->getShowFormula()) ) )
445 {
446 string = userInput();
447 if (showFormula)
448 *showFormula = true;
449 } else if (!isEmpty()) {
450 Value theValue = sheet()->map()->formatter()->formatText(value(), style.formatType(), style.precision(),
451 style.floatFormat(), style.prefix(),
452 style.postfix(), style.currency().symbol(),
453 style.customFormat(), style.thousandsSep());
454 if (v) *v = theValue;
455 string = theValue.asString();
456 if (showFormula)
457 *showFormula = false;
458 }
459 return string;
460}
461
462
463// Return the value of this cell.
464//
465const Value Cell::value() const
466{
467 return sheet()->cellStorage()->value(d->column, d->row);
468}
469
470
471// Set the value of this cell.
472//
473void Cell::setValue(const Value& value)
474{
475 sheet()->cellStorage()->setValue(d->column, d->row, value);
476}
477
478
479QSharedPointer<QTextDocument> Cell::richText() const
480{
481 return sheet()->cellStorage()->richText(d->column, d->row);
482}
483
484void Cell::setRichText(QSharedPointer<QTextDocument> text)
485{
486 sheet()->cellStorage()->setRichText(d->column, d->row, text);
487}
488
489// FIXME: Continue commenting and cleaning here (ingwa)
490
491
492void Cell::copyFormat(const Cell& cell)
493{
494 Q_ASSERT(!isNull()); // trouble ahead...
495 Q_ASSERT(!cell.isNull());
496 Value value = this->value();
497 value.setFormat(cell.value().format());
498 sheet()->cellStorage()->setValue(d->column, d->row, value);
499 if (!style().isDefault() || !cell.style().isDefault())
500 setStyle(cell.style());
501 if (!conditions().isEmpty() || !cell.conditions().isEmpty())
502 setConditions(cell.conditions());
503}
504
505void Cell::copyAll(const Cell& cell)
506{
507 Q_ASSERT(!isNull()); // trouble ahead...
508 Q_ASSERT(!cell.isNull());
509 copyFormat(cell);
510 copyContent(cell);
511 if (!comment().isEmpty() || !cell.comment().isEmpty())
512 setComment(cell.comment());
513 if (!validity().isEmpty() || !cell.validity().isEmpty())
514 setValidity(cell.validity());
515}
516
517void Cell::copyContent(const Cell& cell)
518{
519 Q_ASSERT(!isNull()); // trouble ahead...
520 Q_ASSERT(!cell.isNull());
521 if (cell.isFormula()) {
522 // change all the references, e.g. from A1 to A3 if copying
523 // from e.g. B2 to B4
524 Formula formula(sheet(), *this);
525 formula.setExpression(decodeFormula(cell.encodeFormula()));
526 setFormula(formula);
527 } else {
528 // copy the user input
529 sheet()->cellStorage()->setUserInput(d->column, d->row, cell.userInput());
530 }
531 // copy the value in both cases
532 sheet()->cellStorage()->setValue(d->column, d->row, cell.value());
533}
534
535bool Cell::needsPrinting() const
536{
537 if (!userInput().trimmed().isEmpty())
538 return true;
539 if (!comment().trimmed().isEmpty())
540 return true;
541
542 const Style style = effectiveStyle();
543
544 // Cell borders?
545 if (style.hasAttribute(Style::TopPen) ||
546 style.hasAttribute(Style::LeftPen) ||
547 style.hasAttribute(Style::RightPen) ||
548 style.hasAttribute(Style::BottomPen) ||
549 style.hasAttribute(Style::FallDiagonalPen) ||
550 style.hasAttribute(Style::GoUpDiagonalPen))
551 return true;
552
553 // Background color or brush?
554 if (style.hasAttribute(Style::BackgroundBrush)) {
555 QBrush brush = style.backgroundBrush();
556
557 // Only brushes that are visible (ie. they have a brush style
558 // and are not white) need to be drawn
559 if ((brush.style() != Qt::NoBrush) &&
560 (brush.color() != Qt::white || !brush.texture().isNull()))
561 return true;
562 }
563
564 if (style.hasAttribute(Style::BackgroundColor)) {
565 kDebug(36004) << "needsPrinting: Has background color";
566 QColor backgroundColor = style.backgroundColor();
567
568 // We don't need to print anything, if the background is white opaque or fully transparent.
569 if (!(backgroundColor == Qt::white || backgroundColor.alpha() == 0))
570 return true;
571 }
572
573 return false;
574}
575
576
577QString Cell::encodeFormula(bool fixedReferences) const
578{
579 if (!isFormula())
580 return QString();
581
582 QString result('=');
583 const Tokens tokens = formula().tokens();
584 for (int i = 0; i < tokens.count(); ++i) {
585 const Token token = tokens[i];
586 switch (token.type()) {
587 case Token::Cell:
588 case Token::Range: {
589 if (sheet()->map()->namedAreaManager()->contains(token.text())) {
590 result.append(token.text()); // simply keep the area name
591 break;
592 }
593 const Region region(token.text(), sheet()->map());
594 // Actually, a contiguous region, but the fixation is needed
595 Region::ConstIterator end = region.constEnd();
596 for (Region::ConstIterator it = region.constBegin(); it != end; ++it) {
597 if (!(*it)->isValid())
598 continue;
599 if ((*it)->type() == Region::Element::Point) {
600 if ((*it)->sheet())
601 result.append((*it)->sheet()->sheetName() + '!');
602 const QPoint pos = (*it)->rect().topLeft();
603 if ((*it)->isColumnFixed())
604 result.append(QString("$%1").arg(pos.x()));
605 else if (fixedReferences)
606 result.append(QChar(0xA7) + QString("%1").arg(pos.x()));
607 else
608 result.append(QString("#%1").arg(pos.x() - (int)d->column));
609 if ((*it)->isRowFixed())
610 result.append(QString("$%1#").arg(pos.y()));
611 else if (fixedReferences)
612 result.append(QChar(0xA7) + QString("%1#").arg(pos.y()));
613 else
614 result.append(QString("#%1#").arg(pos.y() - (int)d->row));
615 } else { // ((*it)->type() == Region::Range)
616 if ((*it)->sheet())
617 result.append((*it)->sheet()->sheetName() + '!');
618 QPoint pos = (*it)->rect().topLeft();
619 if ((*it)->isLeftFixed())
620 result.append(QString("$%1").arg(pos.x()));
621 else if (fixedReferences)
622 result.append(QChar(0xA7) + QString("%1").arg(pos.x()));
623 else
624 result.append(QString("#%1").arg(pos.x() - (int)d->column));
625 if ((*it)->isTopFixed())
626 result.append(QString("$%1#").arg(pos.y()));
627 else if (fixedReferences)
628 result.append(QChar(0xA7) + QString("%1#").arg(pos.y()));
629 else
630 result.append(QString("#%1#").arg(pos.y() - (int)d->row));
631 result.append(':');
632 pos = (*it)->rect().bottomRight();
633 if ((*it)->isRightFixed())
634 result.append(QString("$%1").arg(pos.x()));
635 else if (fixedReferences)
636 result.append(QChar(0xA7) + QString("%1").arg(pos.x()));
637 else
638 result.append(QString("#%1").arg(pos.x() - (int)d->column));
639 if ((*it)->isBottomFixed())
640 result.append(QString("$%1#").arg(pos.y()));
641 else if (fixedReferences)
642 result.append(QChar(0xA7) + QString("%1#").arg(pos.y()));
643 else
644 result.append(QString("#%1#").arg(pos.y() - (int)d->row));
645 }
646 }
647 break;
648 }
649 default: {
650 result.append(token.text());
651 break;
652 }
653 }
654 }
655 //kDebug() << result;
656 return result;
657}
658
659QString Cell::decodeFormula(const QString &_text) const
660{
661 QString erg;
662 unsigned int pos = 0;
663 const unsigned int length = _text.length();
664
665 if (_text.isEmpty())
666 return QString();
667
668 while (pos < length) {
669 if (_text[pos] == '"') {
670 erg += _text[pos++];
671 while (pos < length && _text[pos] != '"') {
672 erg += _text[pos++];
673 // Allow escaped double quotes (\")
674 if (pos < length && _text[pos] == '\\' && _text[pos+1] == '"') {
675 erg += _text[pos++];
676 erg += _text[pos++];
677 }
678 }
679 if (pos < length)
680 erg += _text[pos++];
681 } else if (_text[pos] == '#' || _text[pos] == '$' || _text[pos] == QChar(0xA7)) {
682 bool abs1 = false;
683 bool abs2 = false;
684 bool era1 = false; // if 1st is relative but encoded absolutely
685 bool era2 = false;
686
687 QChar _t = _text[pos++];
688 if (_t == '$')
689 abs1 = true;
690 else if (_t == QChar(0xA7))
691 era1 = true;
692
693 int col = 0;
694 unsigned int oldPos = pos;
695 while (pos < length && (_text[pos].isDigit() || _text[pos] == '-')) ++pos;
696 if (pos != oldPos)
697 col = _text.mid(oldPos, pos - oldPos).toInt();
698 if (!abs1 && !era1)
699 col += d->column;
700 // Skip '#' or '$'
701
702 _t = _text[pos++];
703 if (_t == '$')
704 abs2 = true;
705 else if (_t == QChar(0xA7))
706 era2 = true;
707
708 int row = 0;
709 oldPos = pos;
710 while (pos < length && (_text[pos].isDigit() || _text[pos] == '-')) ++pos;
711 if (pos != oldPos)
712 row = _text.mid(oldPos, pos - oldPos).toInt();
713 if (!abs2 && !era2)
714 row += d->row;
715 // Skip '#' or '$'
716 ++pos;
717 if (row < 1 || col < 1 || row > KS_rowMax || col > KS_colMax) {
718 kDebug(36003) << "Cell::decodeFormula: row or column out of range (col:" << col << " | row:" << row << ')';
719 erg += Value::errorREF().errorMessage();
720 } else {
721 if (abs1)
722 erg += '$';
723 erg += Cell::columnName(col); //Get column text
724
725 if (abs2)
726 erg += '$';
727 erg += QString::number(row);
728 }
729 } else
730 erg += _text[pos++];
731 }
732
733 return erg;
734}
735
736
737// ----------------------------------------------------------------
738// Formula handling
739
740
741bool Cell::makeFormula()
742{
743// kDebug(36002) ;
744
745 // sanity check
746 if (!isFormula())
747 return false;
748
749 // parse the formula and check for errors
750 if (!formula().isValid()) {
751 sheet()->showStatusMessage(i18n("Parsing of formula in cell %1 failed.", fullName()));
752 setValue(Value::errorPARSE());
753 return false;
754 }
755 return true;
756}
757
758int Cell::effectiveAlignX() const
759{
760 const Style style = effectiveStyle();
761 int align = style.halign();
762 if (align == Style::HAlignUndefined) {
763 //numbers should be right-aligned by default, as well as BiDi text
764 if ((style.formatType() == Format::Text) || value().isString())
765 align = (displayText().isRightToLeft()) ? Style::Right : Style::Left;
766 else {
767 Value val = value();
768 while (val.isArray()) val = val.element(0, 0);
769 if (val.isBoolean() || val.isNumber())
770 align = Style::Right;
771 else
772 align = Style::Left;
773 }
774 }
775 return align;
776}
777
778double Cell::width() const
779{
780 const int rightCol = d->column + mergedXCells();
781 double width = 0.0;
782 for (int col = d->column; col <= rightCol; ++col)
783 width += sheet()->columnFormat(col)->width();
784 return width;
785}
786
787double Cell::height() const
788{
789 const int bottomRow = d->row + mergedYCells();
790 return sheet()->rowFormats()->totalRowHeight(d->row, bottomRow);
791}
792
793// parses the text
794void Cell::parseUserInput(const QString& text)
795{
796// kDebug() ;
797
798 // empty string?
799 if (text.isEmpty()) {
800 setValue(Value::empty());
801 setUserInput(text);
802 setFormula(Formula::empty());
803 return;
804 }
805
806 // a formula?
807 if (text[0] == '=') {
808 Formula formula(sheet(), *this);
809 formula.setExpression(text);
810 setFormula(formula);
811
812 // parse the formula and check for errors
813 if (!formula.isValid()) {
814 sheet()->showStatusMessage(i18n("Parsing of formula in cell %1 failed.", fullName()));
815 setValue(Value::errorPARSE());
816 return;
817 }
818 return;
819 }
820
821 // keep the old formula and value for the case, that validation fails
822 const Formula oldFormula = formula();
823 const QString oldUserInput = userInput();
824 const Value oldValue = value();
825
826 // here, the new value is not a formula anymore; clear an existing one
827 setFormula(Formula());
828
829 Value value;
830 if (style().formatType() == Format::Text)
831 value = Value(QString(text));
832 else {
833 // Parses the text and return the appropriate value.
834 value = sheet()->map()->parser()->parse(text);
835
836#if 0
837 // Parsing as time acts like an autoformat: we even change the input text
838 // [h]:mm:ss -> might get set by ValueParser
839 if (isTime() && (formatType() != Format::Time7))
840 setUserInput(locale()->formatTime(value().asDateTime(sheet()->map()->calculationSettings()).time(), true));
841#endif
842
843 // convert first letter to uppercase ?
844 if (sheet()->getFirstLetterUpper() && value.isString() && !text.isEmpty()) {
845 QString str = value.asString();
846 value = Value(str[0].toUpper() + str.right(str.length() - 1));
847 }
848 }
849 // set the new value
850 setUserInput(text);
851 setValue(value);
852
853 // validation
854 if (!sheet()->isLoading()) {
855 Validity validity = this->validity();
856 if (!validity.testValidity(this)) {
857 kDebug(36003) << "Validation failed";
858 //reapply old value if action == stop
859 setFormula(oldFormula);
860 setUserInput(oldUserInput);
861 setValue(oldValue);
862 }
863 }
864}
865
866QString Cell::link() const
867{
868 return sheet()->cellStorage()->link(d->column, d->row);
869}
870
871void Cell::setLink(const QString& link)
872{
873 sheet()->cellStorage()->setLink(d->column, d->row, link);
874
875 if (!link.isEmpty() && userInput().isEmpty())
876 parseUserInput(link);
877}
878
879bool Cell::isDate() const
880{
881 const Format::Type t = style().formatType();
882 return (Format::isDate(t) || ((t == Format::Generic) && (value().format() == Value::fmt_Date)));
883}
884
885bool Cell::isTime() const
886{
887 const Format::Type t = style().formatType();
888 return (Format::isTime(t) || ((t == Format::Generic) && (value().format() == Value::fmt_Time)));
889}
890
891bool Cell::isText() const
892{
893 const Format::Type t = style().formatType();
894 return t == Format::Text;
895}
896
897// Return true if this cell is part of a merged cell, but not the
898// master cell.
899
900bool Cell::isPartOfMerged() const
901{
902 return sheet()->cellStorage()->isPartOfMerged(d->column, d->row);
903}
904
905Cell Cell::masterCell() const
906{
907 return sheet()->cellStorage()->masterCell(d->column, d->row);
908}
909
910// Merge a number of cells, i.e. make this cell obscure a number of
911// other cells. If _x and _y == 0, then the merging is removed.
912void Cell::mergeCells(int _col, int _row, int _x, int _y)
913{
914 sheet()->cellStorage()->mergeCells(_col, _row, _x, _y);
915}
916
917bool Cell::doesMergeCells() const
918{
919 return sheet()->cellStorage()->doesMergeCells(d->column, d->row);
920}
921
922int Cell::mergedXCells() const
923{
924 return sheet()->cellStorage()->mergedXCells(d->column, d->row);
925}
926
927int Cell::mergedYCells() const
928{
929 return sheet()->cellStorage()->mergedYCells(d->column, d->row);
930}
931
932bool Cell::isLocked() const
933{
934 return sheet()->cellStorage()->isLocked(d->column, d->row);
935}
936
937QRect Cell::lockedCells() const
938{
939 return sheet()->cellStorage()->lockedCells(d->column, d->row);
940}
941
942
943// ================================================================
944// Saving and loading
945
946
947QDomElement Cell::save(QDomDocument& doc, int xOffset, int yOffset, bool era)
948{
949 // Save the position of this cell
950 QDomElement cell = doc.createElement("cell");
951 cell.setAttribute("row", row() - yOffset);
952 cell.setAttribute("column", column() - xOffset);
953
954 //
955 // Save the formatting information
956 //
957 QDomElement formatElement(doc.createElement("format"));
958 style().saveXML(doc, formatElement, sheet()->map()->styleManager());
959 if (formatElement.hasChildNodes() || formatElement.attributes().length()) // don't save empty tags
960 cell.appendChild(formatElement);
961
962 if (doesMergeCells()) {
963 if (mergedXCells())
964 formatElement.setAttribute("colspan", mergedXCells());
965 if (mergedYCells())
966 formatElement.setAttribute("rowspan", mergedYCells());
967 }
968
969 Conditions conditions = this->conditions();
970 if (!conditions.isEmpty()) {
971 QDomElement conditionElement = conditions.saveConditions(doc, sheet()->map()->converter());
972 if (!conditionElement.isNull())
973 cell.appendChild(conditionElement);
974 }
975
976 Validity validity = this->validity();
977 if (!validity.isEmpty()) {
978 QDomElement validityElement = validity.saveXML(doc, sheet()->map()->converter());
979 if (!validityElement.isNull())
980 cell.appendChild(validityElement);
981 }
982
983 const QString comment = this->comment();
984 if (!comment.isEmpty()) {
985 QDomElement commentElement = doc.createElement("comment");
986 commentElement.appendChild(doc.createCDATASection(comment));
987 cell.appendChild(commentElement);
988 }
989
990 //
991 // Save the text
992 //
993 if (!userInput().isEmpty()) {
994 // Formulas need to be encoded to ensure that they
995 // are position independent.
996 if (isFormula()) {
997 QDomElement txt = doc.createElement("text");
998 // if we are cutting to the clipboard, relative references need to be encoded absolutely
999 txt.appendChild(doc.createTextNode(encodeFormula(era)));
1000 cell.appendChild(txt);
1001
1002 /* we still want to save the results of the formula */
1003 QDomElement formulaResult = doc.createElement("result");
1004 saveCellResult(doc, formulaResult, displayText());
1005 cell.appendChild(formulaResult);
1006
1007 } else if (!link().isEmpty()) {
1008 // KSpread pre 1.4 saves link as rich text, marked with first char '
1009 // Have to be saved in some CDATA section because of too many special charatcers.
1010 QDomElement txt = doc.createElement("text");
1011 QString qml = "!<a href=\"" + link() + "\">" + userInput() + "</a>";
1012 txt.appendChild(doc.createCDATASection(qml));
1013 cell.appendChild(txt);
1014 } else {
1015 // Save the cell contents (in a locale-independent way)
1016 QDomElement txt = doc.createElement("text");
1017 saveCellResult(doc, txt, userInput());
1018 cell.appendChild(txt);
1019 }
1020 }
1021 if (cell.hasChildNodes() || cell.attributes().length() > 2) // don't save empty tags
1022 // (the >2 is due to "row" and "column" attributes)
1023 return cell;
1024 else
1025 return QDomElement();
1026}
1027
1028bool Cell::saveCellResult(QDomDocument& doc, QDomElement& result,
1029 QString str)
1030{
1031 QString dataType = "Other"; // fallback
1032
1033 if (value().isNumber()) {
1034 if (isDate()) {
1035 // serial number of date
1036 QDate dd = value().asDateTime(sheet()->map()->calculationSettings()).date();
1037 dataType = "Date";
1038 str = "%1/%2/%3";
1039 str = str.arg(dd.year()).arg(dd.month()).arg(dd.day());
1040 } else if (isTime()) {
1041 // serial number of time
1042 dataType = "Time";
1043 str = value().asDateTime(sheet()->map()->calculationSettings()).time().toString();
1044 } else {
1045 // real number
1046 dataType = "Num";
1047 if (value().isInteger())
1048 str = QString::number(value().asInteger());
1049 else
1050 str = QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG);
1051 }
1052 }
1053
1054 if (value().isBoolean()) {
1055 dataType = "Bool";
1056 str = value().asBoolean() ? "true" : "false";
1057 }
1058
1059 if (value().isString()) {
1060 dataType = "Str";
1061 str = value().asString();
1062 }
1063
1064 result.setAttribute("dataType", dataType);
1065
1066 const QString displayText = this->displayText();
1067 if (!displayText.isEmpty())
1068 result.setAttribute("outStr", displayText);
1069 result.appendChild(doc.createTextNode(str));
1070
1071 return true; /* really isn't much of a way for this function to fail */
1072}
1073
1074void Cell::saveOdfAnnotation(KoXmlWriter &xmlwriter)
1075{
1076 const QString comment = this->comment();
1077 if (!comment.isEmpty()) {
1078 //<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">
1079 xmlwriter.startElement("office:annotation");
1080 const QStringList text = comment.split('\n', QString::SkipEmptyParts);
1081 for (QStringList::ConstIterator it = text.begin(); it != text.end(); ++it) {
1082 xmlwriter.startElement("text:p");
1083 xmlwriter.addTextNode(*it);
1084 xmlwriter.endElement();
1085 }
1086 xmlwriter.endElement();
1087 }
1088}
1089
1090QString Cell::saveOdfCellStyle(KoGenStyle &currentCellStyle, KoGenStyles &mainStyles)
1091{
1092 const Conditions conditions = this->conditions();
1093 if (!conditions.isEmpty()) {
1094 // this has to be an automatic style
1095 currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell");
1096 conditions.saveOdfConditions(currentCellStyle, sheet()->map()->converter());
1097 }
1098 return style().saveOdf(currentCellStyle, mainStyles, d->sheet->map()->styleManager());
1099}
1100
1101
1102bool Cell::saveOdf(int row, int column, int &repeated,
1103 OdfSavingContext& tableContext)
1104{
1105 KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter();
1106 KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles();
1107
1108 // see: OpenDocument, 8.1.3 Table Cell
1109 if (!isPartOfMerged())
1110 xmlwriter.startElement("table:table-cell");
1111 else
1112 xmlwriter.startElement("table:covered-table-cell");
1113#if 0
1114 //add font style
1115 QFont font;
1116 Value const value(cell.value());
1117 if (!cell.isDefault()) {
1118 font = cell.format()->textFont(i, row);
1119 m_styles.addFont(font);
1120
1121 if (cell.format()->hasProperty(Style::SComment))
1122 hasComment = true;
1123 }
1124#endif
1125 // NOTE save the value before the style as long as the Formatter does not work correctly
1126 if (link().isEmpty())
1127 saveOdfValue(xmlwriter);
1128
1129 const Style cellStyle = style();
1130
1131 // Either there's no column and row default and the style's not the default style,
1132 // or the style is different to one of them. The row default takes precedence.
1133 if ((!tableContext.rowDefaultStyles.contains(row) &&
1134 !tableContext.columnDefaultStyles.contains(column) &&
1135 !(cellStyle.isDefault() && conditions().isEmpty())) ||
1136 (tableContext.rowDefaultStyles.contains(row) && tableContext.rowDefaultStyles[row] != cellStyle) ||
1137 (tableContext.columnDefaultStyles.contains(column) && tableContext.columnDefaultStyles[column] != cellStyle)) {
1138 KoGenStyle currentCellStyle; // the type determined in saveOdfCellStyle
1139 QString styleName = saveOdfCellStyle(currentCellStyle, mainStyles);
1140 // skip 'table:style-name' attribute for the default style
1141 if (!currentCellStyle.isDefaultStyle()) {
1142 if (!styleName.isEmpty())
1143 xmlwriter.addAttribute("table:style-name", styleName);
1144 }
1145 }
1146
1147 // group empty cells with the same style
1148 const QString comment = this->comment();
1149 if (isEmpty() && comment.isEmpty() && !isPartOfMerged() && !doesMergeCells() &&
1150 !tableContext.cellHasAnchoredShapes(sheet(), row, column)) {
1151 bool refCellIsDefault = isDefault();
1152 int j = column + 1;
1153 Cell nextCell = sheet()->cellStorage()->nextInRow(column, row);
1154 while (!nextCell.isNull()) {
1155 // if
1156 // the next cell is not the adjacent one
1157 // or
1158 // the next cell is not empty
1159 if (nextCell.column() != j || (!nextCell.isEmpty() || tableContext.cellHasAnchoredShapes(sheet(), row, column))) {
1160 if (refCellIsDefault) {
1161 // if the origin cell was a default cell,
1162 // we count the default cells
1163 repeated = nextCell.column() - j + 1;
1164
1165 // check if any of the empty/default cells we skipped contained anchored shapes
1166 int shapeColumn = tableContext.nextAnchoredShape(sheet(), row, column);
1167 if (shapeColumn) {
1168 repeated = qMin(repeated, shapeColumn - column);
1169 }
1170 }
1171 // otherwise we just stop here to process the adjacent
1172 // cell in the next iteration of the outer loop
1173 // (in Sheet::saveOdfCells)
1174 break;
1175 }
1176
1177 if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() ||
1178 !nextCell.comment().isEmpty() || tableContext.cellHasAnchoredShapes(sheet(), row, nextCell.column()) ||
1179 !(nextCell.style() == cellStyle && nextCell.conditions() == conditions())) {
1180 break;
1181 }
1182 ++repeated;
1183 // get the next cell and set the index to the adjacent cell
1184 nextCell = sheet()->cellStorage()->nextInRow(j++, row);
1185 }
1186 //kDebug(36003) << "Cell::saveOdf: empty cell in column" << column
1187 //<< "repeated" << repeated << "time(s)" << endl;
1188
1189 if (repeated > 1)
1190 xmlwriter.addAttribute("table:number-columns-repeated", QString::number(repeated));
1191 }
1192
1193 Validity validity = Cell(sheet(), column, row).validity();
1194 if (!validity.isEmpty()) {
1195 GenValidationStyle styleVal(&validity, sheet()->map()->converter());
1196 xmlwriter.addAttribute("table:validation-name", tableContext.valStyle.insert(styleVal));
1197 }
1198 if (isFormula()) {
1199 //kDebug(36003) <<"Formula found";
1200 QString formula = Odf::encodeFormula(userInput(), locale());
1201 xmlwriter.addAttribute("table:formula", formula);
1202 }
1203
1204 if (doesMergeCells()) {
1205 int colSpan = mergedXCells() + 1;
1206 int rowSpan = mergedYCells() + 1;
1207
1208 if (colSpan > 1)
1209 xmlwriter.addAttribute("table:number-columns-spanned", QString::number(colSpan));
1210
1211 if (rowSpan > 1)
1212 xmlwriter.addAttribute("table:number-rows-spanned", QString::number(rowSpan));
1213 }
1214
1215 saveOdfAnnotation(xmlwriter);
1216
1217 if (!isFormula() && !link().isEmpty()) {
1218 //kDebug(36003)<<"Link found";
1219 xmlwriter.startElement("text:p");
1220 xmlwriter.startElement("text:a");
1221 const QString url = link();
1222 //Reference cell is started by '#'
1223 if (Util::localReferenceAnchor(url))
1224 xmlwriter.addAttribute("xlink:href", ('#' + url));
1225 else
1226 xmlwriter.addAttribute("xlink:href", url);
1227 xmlwriter.addAttribute("xlink:type", "simple");
1228 xmlwriter.addTextNode(userInput());
1229 xmlwriter.endElement();
1230 xmlwriter.endElement();
1231 }
1232
1233 if (!isEmpty() && link().isEmpty()) {
1234 QSharedPointer<QTextDocument> doc = richText();
1235 if (doc) {
1236 QTextCharFormat format = style().asCharFormat();
1237 ((KoCharacterStyle *)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
1238
1239 KoTextWriter writer(tableContext.shapeContext);
1240
1241 writer.write(doc.data(), 0);
1242 } else {
1243 xmlwriter.startElement("text:p");
1244 xmlwriter.addTextNode(displayText().toUtf8());
1245 xmlwriter.endElement();
1246 }
1247 }
1248
1249 // flake
1250 // Save shapes that are anchored to this cell.
1251 // see: OpenDocument, 2.3.1 Text Documents
1252 // see: OpenDocument, 9.2 Drawing Shapes
1253 if (tableContext.cellHasAnchoredShapes(sheet(), row, column)) {
1254 const QList<KoShape*> shapes = tableContext.cellAnchoredShapes(sheet(), row, column);
1255 for (int i = 0; i < shapes.count(); ++i) {
1256 KoShape* const shape = shapes[i];
1257 const QPointF bottomRight = shape->boundingRect().bottomRight();
1258 qreal endX = 0.0;
1259 qreal endY = 0.0;
1260 const int scol = sheet()->leftColumn(bottomRight.x(), endX);
1261 const int srow = sheet()->topRow(bottomRight.y(), endY);
1262 qreal offsetX = sheet()->columnPosition(column);
1263 qreal offsetY = sheet()->rowPosition(row);
1264 tableContext.shapeContext.addShapeOffset(shape, QTransform::fromTranslate(-offsetX, -offsetY));
1265 shape->setAdditionalAttribute("table:end-cell-address", Region(QPoint(scol, srow)).saveOdf());
1266 shape->setAdditionalAttribute("table:end-x", QString::number(bottomRight.x() - endX) + "pt");
1267 shape->setAdditionalAttribute("table:end-y", QString::number(bottomRight.y() - endY) + "pt");
1268 shape->saveOdf(tableContext.shapeContext);
1269 shape->removeAdditionalAttribute("table:end-cell-address");
1270 shape->removeAdditionalAttribute("table:end-x");
1271 shape->removeAdditionalAttribute("table:end-y");
1272 tableContext.shapeContext.removeShapeOffset(shape);
1273 }
1274 }
1275
1276 xmlwriter.endElement();
1277 return true;
1278}
1279
1280void Cell::saveOdfValue(KoXmlWriter &xmlWriter)
1281{
1282 switch (value().format()) {
1283 case Value::fmt_None: break; //NOTHING HERE
1284 case Value::fmt_Boolean: {
1285 xmlWriter.addAttribute("office:value-type", "boolean");
1286 xmlWriter.addAttribute("office:boolean-value", (value().asBoolean() ?
1287 "true" : "false"));
1288 break;
1289 }
1290 case Value::fmt_Number: {
1291 if (isDate()) {
1292 xmlWriter.addAttribute("office:value-type", "date");
1293 xmlWriter.addAttribute("office:date-value",
1294 value().asDate(sheet()->map()->calculationSettings()).toString(Qt::ISODate));
1295 } else if (isText()) {
1296 xmlWriter.addAttribute("office:value-type", "string");
1297 if (value().isInteger())
1298 xmlWriter.addAttribute("office:string-value", QString::number(value().asInteger()));
1299 else
1300 xmlWriter.addAttribute("office:string-value", QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG));
1301 } else {
1302 xmlWriter.addAttribute("office:value-type", "float");
1303 if (value().isInteger())
1304 xmlWriter.addAttribute("office:value", QString::number(value().asInteger()));
1305 else
1306 xmlWriter.addAttribute("office:value", QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG));
1307 }
1308 break;
1309 }
1310 case Value::fmt_Percent: {
1311 xmlWriter.addAttribute("office:value-type", "percentage");
1312 xmlWriter.addAttribute("office:value",
1313 QString::number((double) numToDouble(value().asFloat())));
1314 break;
1315 }
1316 case Value::fmt_Money: {
1317 xmlWriter.addAttribute("office:value-type", "currency");
1318 const Style style = this->style();
1319 if (style.hasAttribute(Style::CurrencyFormat)) {
1320 Currency currency = style.currency();
1321 xmlWriter.addAttribute("office:currency", currency.code());
1322 }
1323 xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value().asFloat())));
1324 break;
1325 }
1326 case Value::fmt_DateTime: break; //NOTHING HERE
1327 case Value::fmt_Date: {
1328 if (isTime()) {
1329 xmlWriter.addAttribute("office:value-type", "time");
1330 xmlWriter.addAttribute("office:time-value",
1331 value().asTime(sheet()->map()->calculationSettings()).toString("'PT'hh'H'mm'M'ss'S'"));
1332 } else {
1333 xmlWriter.addAttribute("office:value-type", "date");
1334 xmlWriter.addAttribute("office:date-value",
1335 value().asDate(sheet()->map()->calculationSettings()).toString(Qt::ISODate));
1336 }
1337 break;
1338 }
1339 case Value::fmt_Time: {
1340 xmlWriter.addAttribute("office:value-type", "time");
1341 xmlWriter.addAttribute("office:time-value",
1342 value().asTime(sheet()->map()->calculationSettings()).toString("'PT'hh'H'mm'M'ss'S'"));
1343 break;
1344 }
1345 case Value::fmt_String: {
1346 xmlWriter.addAttribute("office:value-type", "string");
1347 xmlWriter.addAttribute("office:string-value", value().asString());
1348 break;
1349 }
1350 };
1351}
1352
1353bool Cell::loadOdf(const KoXmlElement& element, OdfLoadingContext& tableContext,
1354 const Styles& autoStyles, const QString& cellStyleName,
1355 QList<ShapeLoadingData>& shapeData)
1356{
1357 static const QString sFormula = QString::fromLatin1("formula");
1358 static const QString sValidationName = QString::fromLatin1("validation-name");
1359 static const QString sValueType = QString::fromLatin1("value-type");
1360 static const QString sBoolean = QString::fromLatin1("boolean");
1361 static const QString sBooleanValue = QString::fromLatin1("boolean-value");
1362 static const QString sTrue = QString::fromLatin1("true");
1363 static const QString sFalse = QString::fromLatin1("false");
1364 static const QString sFloat = QString::fromLatin1("float");
1365 static const QString sValue = QString::fromLatin1("value");
1366 static const QString sCurrency = QString::fromLatin1("currency");
1367 static const QString sPercentage = QString::fromLatin1("percentage");
1368 static const QString sDate = QString::fromLatin1("date");
1369 static const QString sDateValue = QString::fromLatin1("date-value");
1370 static const QString sTime = QString::fromLatin1("time");
1371 static const QString sTimeValue = QString::fromLatin1("time-value");
1372 static const QString sString = QString::fromLatin1("string");
1373 static const QString sStringValue = QString::fromLatin1("string-value");
1374 static const QString sNumberColumnsSpanned = QString::fromLatin1("number-columns-spanned");
1375 static const QString sNumberRowsSpanned = QString::fromLatin1("number-rows-spanned");
1376 static const QString sAnnotation = QString::fromLatin1("annotation");
1377 static const QString sP = QString::fromLatin1("p");
1378
1379 static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" << "of:" << "msoxl:";
1380
1381 //Search and load each paragraph of text. Each paragraph is separated by a line break.
1382 loadOdfCellText(element, tableContext, autoStyles, cellStyleName);
1383
1384 //
1385 // formula
1386 //
1387 bool isFormula = false;
1388 if (element.hasAttributeNS(KoXmlNS::table, sFormula)) {
1389 isFormula = true;
1390 QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, QString()));
1391 // kDebug(36003) << "cell:" << name() << "formula :" << oasisFormula;
1392 // each spreadsheet application likes to safe formulas with a different namespace
1393 // prefix, so remove all of them
1394 QString namespacePrefix;
1395 foreach(const QString &prefix, formulaNSPrefixes) {
1396 if (oasisFormula.startsWith(prefix)) {
1397 oasisFormula = oasisFormula.mid(prefix.length());
1398 namespacePrefix = prefix;
1399 break;
1400 }
1401 }
1402 oasisFormula = Odf::decodeFormula(oasisFormula, locale(), namespacePrefix);
1403 setUserInput(oasisFormula);
1404 } else if (!userInput().isEmpty() && userInput().at(0) == '=') //prepend ' to the text to avoid = to be painted
1405 setUserInput(userInput().prepend('\''));
1406
1407 //
1408 // validation
1409 //
1410 if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) {
1411 const QString validationName = element.attributeNS(KoXmlNS::table, sValidationName, QString());
1412 kDebug(36003) << "cell:" << name() << sValidationName << validationName;
1413 Validity validity;
1414 validity.loadOdfValidation(this, validationName, tableContext);
1415 if (!validity.isEmpty())
1416 setValidity(validity);
1417 }
1418
1419 //
1420 // value type
1421 //
1422 if (element.hasAttributeNS(KoXmlNS::office, sValueType)) {
1423 const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, QString());
1424 // kDebug(36003) << "cell:" << name() << "value-type:" << valuetype;
1425 if (valuetype == sBoolean) {
1426 const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, QString()).toLower();
1427 if ((val == sTrue) || (val == sFalse))
1428 setValue(Value(val == sTrue));
1429 }
1430
1431 // integer and floating-point value
1432 else if (valuetype == sFloat) {
1433 bool ok = false;
1434 Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok));
1435 if (ok) {
1436 value.setFormat(Value::fmt_Number);
1437 setValue(value);
1438#if 0
1439 Style style;
1440 style.setFormatType(Format::Number);
1441 setStyle(style);
1442#endif
1443 }
1444 // 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
1445 if (!isFormula)
1446 setUserInput(sheet()->map()->converter()->asString(value).asString());
1447 }
1448
1449 // currency value
1450 else if (valuetype == sCurrency) {
1451 bool ok = false;
1452 Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok));
1453 if (ok) {
1454 value.setFormat(Value::fmt_Money);
1455 setValue(value);
1456
1457 Currency currency;
1458 if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) {
1459 currency = Currency(element.attributeNS(KoXmlNS::office, sCurrency, QString()));
1460 }
1461 /* TODO: somehow make this work again, all setStyle calls here will be overwritten by cell styles later
1462 if( style.isEmpty() ) {
1463 Style style;
1464 style.setCurrency(currency);
1465 setStyle(style);
1466 } */
1467 }
1468 } else if (valuetype == sPercentage) {
1469 bool ok = false;
1470 Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok));
1471 if (ok) {
1472 value.setFormat(Value::fmt_Percent);
1473 setValue(value);
1474 if (!isFormula && userInput().isEmpty())
1475 setUserInput(sheet()->map()->converter()->asString(value).asString());
1476// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
1477#if 0
1478 Style style;
1479 style.setFormatType(Format::Percentage);
1480 setStyle(style);
1481#endif
1482 }
1483 } else if (valuetype == sDate) {
1484 QString value = element.attributeNS(KoXmlNS::office, sDateValue, QString());
1485
1486 // "1980-10-15" or "2001-01-01T19:27:41"
1487 int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0;
1488 bool hasTime = false;
1489 bool ok = false;
1490
1491 int p1 = value.indexOf('-');
1492 if (p1 > 0) {
1493 year = value.left(p1).toInt(&ok);
1494 if (ok) {
1495 int p2 = value.indexOf('-', ++p1);
1496 month = value.mid(p1, p2 - p1).toInt(&ok);
1497 if (ok) {
1498 // the date can optionally have a time attached
1499 int p3 = value.indexOf('T', ++p2);
1500 if (p3 > 0) {
1501 hasTime = true;
1502 day = value.mid(p2, p3 - p2).toInt(&ok);
1503 if (ok) {
1504 int p4 = value.indexOf(':', ++p3);
1505 hours = value.mid(p3, p4 - p3).toInt(&ok);
1506 if (ok) {
1507 int p5 = value.indexOf(':', ++p4);
1508 minutes = value.mid(p4, p5 - p4).toInt(&ok);
1509 if (ok)
1510 seconds = value.right(value.length() - p5 - 1).toInt(&ok);
1511 }
1512 }
1513 } else {
1514 day = value.right(value.length() - p2).toInt(&ok);
1515 }
1516 }
1517 }
1518 }
1519
1520 if (ok) {
1521 if (hasTime)
1522 setValue(Value(QDateTime(QDate(year, month, day), QTime(hours, minutes, seconds)), sheet()->map()->calculationSettings()));
1523 else
1524 setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings()));
1525// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
1526//Sebsauer: Fixed now. Value::Format handles it correct.
1527#if 0
1528 Style s;
1529 s.setFormatType(Format::ShortDate);
1530 setStyle(s);
1531#endif
1532 // kDebug(36003) << "cell:" << name() << "Type: date, value:" << value << "Date:" << year << " -" << month << " -" << day;
1533 }
1534 } else if (valuetype == sTime) {
1535 QString value = element.attributeNS(KoXmlNS::office, sTimeValue, QString());
1536
1537 // "PT15H10M12S"
1538 int hours = 0, minutes = 0, seconds = 0;
1539 int l = value.length();
1540 QString num;
1541 bool ok = false;
1542 for (int i = 0; i < l; ++i) {
1543 if (value[i].isNumber()) {
1544 num += value[i];
1545 continue;
1546 } else if (value[i] == 'H')
1547 hours = num.toInt(&ok);
1548 else if (value[i] == 'M')
1549 minutes = num.toInt(&ok);
1550 else if (value[i] == 'S')
1551 seconds = num.toInt(&ok);
1552 else
1553 continue;
1554 //kDebug(36003) << "Num:" << num;
1555 num.clear();
1556 if (!ok)
1557 break;
1558 }
1559
1560 if (ok) {
1561 // Value kval( timeToNum( hours, minutes, seconds ) );
1562 // cell.setValue( kval );
1563 setValue(Value(QTime(hours % 24, minutes, seconds), sheet()->map()->calculationSettings()));
1564// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
1565#if 0
1566 Style style;
1567 style.setFormatType(Format::Time);
1568 setStyle(style);
1569#endif
1570 // kDebug(36003) << "cell:" << name() << "Type: time:" << value << "Hours:" << hours << "," << minutes << "," << seconds;
1571 }
1572 } else if (valuetype == sString) {
1573 if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) {
1574 QString value = element.attributeNS(KoXmlNS::office, sStringValue, QString());
1575 setValue(Value(value));
1576 } else {
1577 // use the paragraph(s) read in before
1578 setValue(Value(userInput()));
1579 }
1580// FIXME Stefan: Should be handled by Value::Format. Verify and remove!
1581#if 0
1582 Style style;
1583 style.setFormatType(Format::Text);
1584 setStyle(style);
1585#endif
1586 } else {
1587 // kDebug(36003) << "cell:" << name() << " Unknown type. Parsing user input.";
1588 // Set the value by parsing the user input.
1589 parseUserInput(userInput());
1590 }
1591 } else { // no value-type attribute
1592 // kDebug(36003) << "cell:" << name() << " No value type specified. Parsing user input.";
1593 // Set the value by parsing the user input.
1594 parseUserInput(userInput());
1595 }
1596
1597 //
1598 // merged cells ?
1599 //
1600 int colSpan = 1;
1601 int rowSpan = 1;
1602 if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) {
1603 bool ok = false;
1604 int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, QString()).toInt(&ok);
1605 if (ok) colSpan = span;
1606 }
1607 if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) {
1608 bool ok = false;
1609 int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, QString()).toInt(&ok);
1610 if (ok) rowSpan = span;
1611 }
1612 if (colSpan > 1 || rowSpan > 1)
1613 mergeCells(d->column, d->row, colSpan - 1, rowSpan - 1);
1614
1615 //
1616 // cell comment/annotation
1617 //
1618 KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, sAnnotation);
1619 if (!annotationElement.isNull()) {
1620 QString comment;
1621 KoXmlNode node = annotationElement.firstChild();
1622 while (!node.isNull()) {
1623 KoXmlElement commentElement = node.toElement();
1624 if (!commentElement.isNull())
1625 if (commentElement.localName() == sP && commentElement.namespaceURI() == KoXmlNS::text) {
1626 if (!comment.isEmpty()) comment.append('\n');
1627 comment.append(commentElement.text());
1628 }
1629
1630 node = node.nextSibling();
1631 }
1632 if (!comment.isEmpty())
1633 setComment(comment);
1634 }
1635
1636 loadOdfObjects(element, tableContext, shapeData);
1637
1638 return true;
1639}
1640
1641// Similar to KoXml::namedItemNS except that children of span tags will be evaluated too.
1642KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString &nsURI, const QString &localName)
1643{
1644 KoXmlNode n = node.firstChild();
1645 for (; !n.isNull(); n = n.nextSibling()) {
1646 if (n.isElement()) {
1647 if (n.localName() == localName && n.namespaceURI() == nsURI) {
1648 return n.toElement();
1649 }
1650 if (n.localName() == "span" && n.namespaceURI() == nsURI) {
1651 KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not recursive
1652 if (!e.isNull()) {
1653 return e;
1654 }
1655 }
1656 }
1657 }
1658 return KoXmlElement();
1659}
1660
1661// recursively goes through all children of parent and returns true if there is any element
1662// in the draw: namespace in this subtree
1663static bool findDrawElements(const KoXmlElement& parent)
1664{
1665 KoXmlElement element;
1666 forEachElement(element , parent) {
1667 if (element.namespaceURI() == KoXmlNS::draw)
1668 return true;
1669 if (findDrawElements(element))
1670 return true;
1671 }
1672 return false;
1673}
1674
1675QString loadOdfCellTextNodes(const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace)
1676{
1677 QString cellText;
1678 bool countedOwnFragments = false;
1679 bool prevWasText = false;
1680 for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
1681 if (n.isText()) {
1682 prevWasText = true;
1683 QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), *stripLeadingSpace);
1684 if (!t.isEmpty()) {
1685 *stripLeadingSpace = t[t.length() - 1].isSpace();
1686 cellText += t;
1687 if (!countedOwnFragments) {
1688 // We only count the number of different parent elements which have text. That is
1689 // so cause different parent-elements may mean different styles which means
1690 // rich-text while the same parent element means the same style so we can easily
1691 // put them together into one string.
1692 countedOwnFragments = true;
1693 ++(*textFragmentCount);
1694 }
1695 }
1696 } else {
1697 KoXmlElement e = n.toElement();
1698 if (!e.isNull()) {
1699 if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() - 1].isSpace()) {
1700 // A trailing space of the cellText collected so far needs to be preserved when
1701 // more text-nodes within the same parent follow but if an element like e.g.
1702 // text:s follows then a trailing space needs to be removed.
1703 cellText.chop(1);
1704 }
1705 prevWasText = false;
1706
1707 // We can optimize some elements like text:s (space), text:tab (tabulator) and
1708 // text:line-break (new-line) to not produce rich-text but add the equivalent
1709 // for them in plain-text.
1710 const bool isTextNs = e.namespaceURI() == KoXmlNS::text;
1711 if (isTextNs && e.localName() == "s") {
1712 const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c", QString()).toInt());
1713 cellText += QString().fill(32, howmany);
1714 } else if (isTextNs && e.localName() == "tab") {
1715 cellText += '\t';
1716 } else if (isTextNs && e.localName() == "line-break") {
1717 cellText += '\n';
1718 ++(*lineCount);
1719 } else if (isTextNs && e.localName() == "span") {
1720 // Nested span-elements means recursive evaluation.
1721 cellText += loadOdfCellTextNodes(e, textFragmentCount, lineCount, hasRichText, stripLeadingSpace);
1722 } else if (!isTextNs ||
1723 ( e.localName() != "annotation" &&
1724 e.localName() != "bookmark" &&
1725 e.localName() != "meta" &&
1726 e.localName() != "tag" )) {
1727 // Seems we have an element we cannot easily translate to a string what
1728 // means it's all rich-text now.
1729 *hasRichText = true;
1730 }
1731 }
1732 }
1733 }
1734 return cellText;
1735}
1736
1737void Cell::loadOdfCellText(const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName)
1738{
1739 //Search and load each paragraph of text. Each paragraph is separated by a line break
1740 KoXmlElement textParagraphElement;
1741 QString cellText;
1742
1743 int lineCount = 0;
1744 bool hasRichText = false;
1745 bool stripLeadingSpace = true;
1746
1747 forEachElement(textParagraphElement , parent) {
1748 if (textParagraphElement.localName() == "p" &&
1749 textParagraphElement.namespaceURI() == KoXmlNS::text) {
1750
1751 // the text:a link could be located within a text:span element
1752 KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, KoXmlNS::text, "a");
1753 if (!textA.isNull() && textA.hasAttributeNS(KoXmlNS::xlink, "href")) {
1754 QString link = textA.attributeNS(KoXmlNS::xlink, "href", QString());
1755 cellText = textA.text();
1756 setUserInput(cellText);
1757 hasRichText = false;
1758 lineCount = 0;
1759 // The value will be set later in loadOdf().
1760 if ((!link.isEmpty()) && (link[0] == '#'))
1761 link.remove(0, 1);
1762 setLink(link);
1763 // Abort here cause we can handle only either a link in a cell or (rich-)text but not both.
1764 break;
1765 }
1766
1767 if (!cellText.isNull())
1768 cellText += '\n';
1769
1770 ++lineCount;
1771 int textFragmentCount = 0;
1772
1773 // Our text could contain formating for value or result of formula or a mix of
1774 // multiple text:span elements with text-nodes and line-break's.
1775 cellText += loadOdfCellTextNodes(textParagraphElement, &textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace);
1776
1777 // If we got text from multiple different sources (e.g. from the text:p and a
1778 // child text:span) then we have very likely rich-text.
1779 if (!hasRichText)
1780 hasRichText = textFragmentCount >= 2;
1781 }
1782 }
1783
1784 if (!cellText.isNull()) {
1785 if (hasRichText && !findDrawElements(parent)) {
1786 // for now we don't support richtext and embedded shapes in the same cell;
1787 // this is because they would currently be loaded twice, once by the KoTextLoader
1788 // and later properly by the cell itself
1789
1790 Style style; style.setDefault();
1791 if (!cellStyleName.isEmpty()) {
1792 if (autoStyles.contains(cellStyleName))
1793 style.merge(autoStyles[cellStyleName]);
1794 else {
1795 const CustomStyle* namedStyle = sheet()->map()->styleManager()->style(cellStyleName);
1796 if (namedStyle)
1797 style.merge(*namedStyle);
1798 }
1799 }
1800
1801 QTextCharFormat format = style.asCharFormat();
1802 ((KoCharacterStyle *)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format);
1803
1804 QSharedPointer<QTextDocument> doc(new QTextDocument);
1805 KoTextDocument(doc.data()).setStyleManager(sheet()->map()->textStyleManager());
1806
1807 Q_ASSERT(tableContext.shapeContext);
1808 KoTextLoader loader(*tableContext.shapeContext);
1809 QTextCursor cursor(doc.data());
1810 loader.loadBody(parent, cursor);
1811
1812 setUserInput(doc->toPlainText());
1813 setRichText(doc);
1814 } else {
1815 setUserInput(cellText);
1816 }
1817 }
1818
1819 // enable word wrapping if multiple lines of text have been found.
1820 if (lineCount >= 2) {
1821 Style newStyle;
1822 newStyle.setWrapText(true);
1823 setStyle(newStyle);
1824 }
1825}
1826
1827void Cell::loadOdfObjects(const KoXmlElement &parent, OdfLoadingContext& tableContext, QList<ShapeLoadingData>& shapeData)
1828{
1829 // Register additional attributes, that identify shapes anchored in cells.
1830 // Their dimensions need adjustment after all rows are loaded,
1831 // because the position of the end cell is not always known yet.
1832 KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
1833 KoXmlNS::table, "end-cell-address",
1834 "table:end-cell-address"));
1835 KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
1836 KoXmlNS::table, "end-x",
1837 "table:end-x"));
1838 KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
1839 KoXmlNS::table, "end-y",
1840 "table:end-y"));
1841
1842 KoXmlElement element;
1843 forEachElement(element, parent) {
1844 if (element.namespaceURI() != KoXmlNS::draw)
1845 continue;
1846
1847 if (element.localName() == "a") {
1848 // It may the case that the object(s) are embedded into a hyperlink so actions are done on
1849 // clicking it/them but since we do not supported objects-with-hyperlinks yet we just fetch
1850 // the inner elements and use them to at least create and show the objects (see bug 249862).
1851 KoXmlElement e;
1852 forEachElement(e, element) {
1853 if (e.namespaceURI() != KoXmlNS::draw)
1854 continue;
1855 ShapeLoadingData data = loadOdfObject(e, *tableContext.shapeContext);
1856 if (data.shape) {
1857 shapeData.append(data);
1858 }
1859 }
1860 } else {
1861 ShapeLoadingData data = loadOdfObject(element, *tableContext.shapeContext);
1862 if (data.shape) {
1863 shapeData.append(data);
1864 }
1865 }
1866 }
1867}
1868
1869ShapeLoadingData Cell::loadOdfObject(const KoXmlElement &element, KoShapeLoadingContext &shapeContext)
1870{
1871 ShapeLoadingData data;
1872 data.shape = 0;
1873 KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext);
1874 if (!shape) {
1875 kDebug(36003) << "Unable to load shape with localName=" << element.localName();
1876 return data;
1877 }
1878
1879 d->sheet->addShape(shape);
1880
1881 // The position is relative to the upper left sheet corner until now. Move it.
1882 QPointF position = shape->position();
1883 // Remember how far we're off from the top-left corner of this cell
1884 double offsetX = position.x();
1885 double offsetY = position.y();
1886 for (int col = 1; col < column(); ++col)
1887 position += QPointF(d->sheet->columnFormat(col)->width(), 0.0);
1888 if (this->row() > 1)
1889 position += QPointF(0.0, d->sheet->rowFormats()->totalRowHeight(1, this->row() - 1));
1890 shape->setPosition(position);
1891
1892 dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(true);
1893
1894 // All three attributes are necessary for cell anchored shapes.
1895 // Otherwise, they are anchored in the sheet.
1896 if (!shape->hasAdditionalAttribute("table:end-cell-address") ||
1897 !shape->hasAdditionalAttribute("table:end-x") ||
1898 !shape->hasAdditionalAttribute("table:end-y")) {
1899 kDebug(36003) << "Not all attributes found, that are necessary for cell anchoring.";
1900 return data;
1901 }
1902
1903 Region endCell(Region::loadOdf(shape->additionalAttribute("table:end-cell-address")),
1904 d->sheet->map(), d->sheet);
1905 if (!endCell.isValid() || !endCell.isSingular())
1906 return data;
1907
1908 QString string = shape->additionalAttribute("table:end-x");
1909 if (string.isNull())
1910 return data;
1911 double endX = KoUnit::parseValue(string);
1912
1913 string = shape->additionalAttribute("table:end-y");
1914 if (string.isNull())
1915 return data;
1916 double endY = KoUnit::parseValue(string);
1917
1918 data.shape = shape;
1919 data.startCell = QPoint(column(), row());
1920 data.offset = QPointF(offsetX, offsetY);
1921 data.endCell = endCell;
1922 data.endPoint = QPointF(endX, endY);
1923
1924 // The column dimensions are already the final ones, but not the row dimensions.
1925 // The default height is used for the not yet loaded rows.
1926 // TODO Stefan: Honor non-default row heights later!
1927 // subtract offset because the accumulated width and height we calculate below starts
1928 // at the top-left corner of this cell, but the shape can have an offset to that corner
1929 QSizeF size = QSizeF(endX - offsetX, endY - offsetY);
1930 for (int col = column(); col < endCell.firstRange().left(); ++col)
1931 size += QSizeF(d->sheet->columnFormat(col)->width(), 0.0);
1932 if (endCell.firstRange().top() > this->row())
1933 size += QSizeF(0.0, d->sheet->rowFormats()->totalRowHeight(this->row(), endCell.firstRange().top() - 1));
1934 shape->setSize(size);
1935
1936 return data;
1937}
1938
1939bool Cell::load(const KoXmlElement & cell, int _xshift, int _yshift,
1940 Paste::Mode mode, Paste::Operation op, bool paste)
1941{
1942 bool ok;
1943
1944 //
1945 // First of all determine in which row and column this
1946 // cell belongs.
1947 //
1948 d->row = cell.attribute("row").toInt(&ok) + _yshift;
1949 if (!ok) return false;
1950 d->column = cell.attribute("column").toInt(&ok) + _xshift;
1951 if (!ok) return false;
1952
1953 // Validation
1954 if (d->row < 1 || d->row > KS_rowMax) {
1955 kDebug(36001) << "Cell::load: Value out of range Cell:row=" << d->row;
1956 return false;
1957 }
1958 if (d->column < 1 || d->column > KS_colMax) {
1959 kDebug(36001) << "Cell::load: Value out of range Cell:column=" << d->column;
1960 return false;
1961 }
1962
1963 //
1964 // Load formatting information.
1965 //
1966 KoXmlElement formatElement = cell.namedItem("format").toElement();
1967 if (!formatElement.isNull() &&
1968 ((mode == Paste::Normal) || (mode == Paste::Format) || (mode == Paste::NoBorder))) {
1969 int mergedXCells = 0;
1970 int mergedYCells = 0;
1971 if (formatElement.hasAttribute("colspan")) {
1972 int i = formatElement.attribute("colspan").toInt(&ok);
1973 if (!ok) return false;
1974 // Validation
1975 if (i < 0 || i > KS_spanMax) {
1976 kDebug(36001) << "Value out of range Cell::colspan=" << i;
1977 return false;
1978 }
1979 if (i)
1980 mergedXCells = i;
1981 }
1982
1983 if (formatElement.hasAttribute("rowspan")) {
1984 int i = formatElement.attribute("rowspan").toInt(&ok);
1985 if (!ok) return false;
1986 // Validation
1987 if (i < 0 || i > KS_spanMax) {
1988 kDebug(36001) << "Value out of range Cell::rowspan=" << i;
1989 return false;
1990 }
1991 if (i)
1992 mergedYCells = i;
1993 }
1994
1995 if (mergedXCells != 0 || mergedYCells != 0)
1996 mergeCells(d->column, d->row, mergedXCells, mergedYCells);
1997
1998 Style style;
1999 if (!style.loadXML(formatElement, mode))
2000 return false;
2001 setStyle(style);
2002 }
2003
2004 //
2005 // Load the condition section of a cell.
2006 //
2007 KoXmlElement conditionsElement = cell.namedItem("condition").toElement();
2008 if (!conditionsElement.isNull()) {
2009 Conditions conditions;
2010 Map *const map = sheet()->map();
2011 ValueParser *const valueParser = map->parser();
2012 conditions.loadConditions(conditionsElement, valueParser);
2013 if (!conditions.isEmpty())
2014 setConditions(conditions);
2015 } else if (paste && (mode == Paste::Normal || mode == Paste::NoBorder)) {
2016 //clear the conditional formatting
2017 setConditions(Conditions());
2018 }
2019
2020 KoXmlElement validityElement = cell.namedItem("validity").toElement();
2021 if (!validityElement.isNull()) {
2022 Validity validity;
2023 if (validity.loadXML(this, validityElement))
2024 setValidity(validity);
2025 } else if (paste && (mode == Paste::Normal || mode == Paste::NoBorder)) {
2026 // clear the validity
2027 setValidity(Validity());
2028 }
2029
2030 //
2031 // Load the comment
2032 //
2033 KoXmlElement comment = cell.namedItem("comment").toElement();
2034 if (!comment.isNull() &&
2035 (mode == Paste::Normal || mode == Paste::Comment || mode == Paste::NoBorder)) {
2036 QString t = comment.text();
2037 //t = t.trimmed();
2038 setComment(t);
2039 }
2040
2041 //
2042 // The real content of the cell is loaded here. It is stored in
2043 // the "text" tag, which contains either a text or a CDATA section.
2044 //
2045 // TODO: make this suck less. We set data twice, in loadCellData, and
2046 // also here. Not good.
2047 KoXmlElement text = cell.namedItem("text").toElement();
2048
2049 if (!text.isNull() &&
2050 (mode == Paste::Normal || mode == Paste::Text || mode == Paste::NoBorder || mode == Paste::Result)) {
2051
2052 /* older versions mistakenly put the datatype attribute on the cell instead
2053 of the text. Just move it over in case we're parsing an old document */
2054 QString dataType;
2055 if (cell.hasAttribute("dataType")) // new docs
2056 dataType = cell.attribute("dataType");
2057
2058 KoXmlElement result = cell.namedItem("result").toElement();
2059 QString txt = text.text();
2060 if ((mode == Paste::Result) && (txt[0] == '='))
2061 // paste text of the element, if we want to paste result
2062 // and the source cell contains a formula
2063 setUserInput(result.text());
2064 else
2065 //otherwise copy everything
2066 loadCellData(text, op, dataType);
2067
2068 if (!result.isNull()) {
2069 QString dataType;
2070 QString t = result.text();
2071
2072 if (result.hasAttribute("dataType"))
2073 dataType = result.attribute("dataType");
2074
2075 // boolean ?
2076 if (dataType == "Bool") {
2077 if (t == "false")
2078 setValue(Value(false));
2079 else if (t == "true")
2080 setValue(Value(true));
2081 } else if (dataType == "Num") {
2082 bool ok = false;
2083 double dd = t.toDouble(&ok);
2084 if (ok)
2085 setValue(Value(dd));
2086 } else if (dataType == "Date") {
2087 bool ok = false;
2088 double dd = t.toDouble(&ok);
2089 if (ok) {
2090 Value value(dd);
2091 value.setFormat(Value::fmt_Date);
2092 setValue(value);
2093 } else {
2094 int pos = t.indexOf('/');
2095 int year = t.mid(0, pos).toInt();
2096 int pos1 = t.indexOf('/', pos + 1);
2097 int month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt();
2098 int day = t.right(t.length() - pos1 - 1).toInt();
2099 QDate date(year, month, day);
2100 if (date.isValid())
2101 setValue(Value(date, sheet()->map()->calculationSettings()));
2102 }
2103 } else if (dataType == "Time") {
2104 bool ok = false;
2105 double dd = t.toDouble(&ok);
2106 if (ok) {
2107 Value value(dd);
2108 value.setFormat(Value::fmt_Time);
2109 setValue(value);
2110 } else {
2111 int hours = -1;
2112 int minutes = -1;
2113 int second = -1;
2114 int pos, pos1;
2115 pos = t.indexOf(':');
2116 hours = t.mid(0, pos).toInt();
2117 pos1 = t.indexOf(':', pos + 1);
2118 minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt();
2119 second = t.right(t.length() - pos1 - 1).toInt();
2120 QTime time(hours, minutes, second);
2121 if (time.isValid())
2122 setValue(Value(time, sheet()->map()->calculationSettings()));
2123 }
2124 } else {
2125 setValue(Value(t));
2126 }
2127 }
2128 }
2129
2130 return true;
2131}
2132
2133bool Cell::loadCellData(const KoXmlElement & text, Paste::Operation op, const QString &_dataType)
2134{
2135 //TODO: use converter()->asString() to generate userInput()
2136
2137 QString t = text.text();
2138 t = t.trimmed();
2139
2140 // A formula like =A1+A2 ?
2141 if ((!t.isEmpty()) && (t[0] == '=')) {
2142 t = decodeFormula(t);
2143 parseUserInput(pasteOperation(t, userInput(), op));
2144
2145 makeFormula();
2146 }
2147 // rich text ?
2148 else if ((!t.isEmpty()) && (t[0] == '!')) {
2149 // KSpread pre 1.4 stores hyperlink as rich text (first char is '!')
2150 // extract the link and the correspoding text
2151 // This is a rather dirty hack, but enough for KSpread generated XML
2152 bool inside_tag = false;
2153 QString qml_text;
2154 QString tag;
2155 QString qml_link;
2156
2157 for (int i = 1; i < t.length(); ++i) {
2158 QChar ch = t[i];
2159 if (ch == '<') {
2160 if (!inside_tag) {
2161 inside_tag = true;
2162 tag.clear();
2163 }
2164 } else if (ch == '>') {
2165 if (inside_tag) {
2166 inside_tag = false;
2167 if (tag.startsWith(QLatin1String("a href=\""), Qt::CaseSensitive) &&
2168 tag.endsWith(QLatin1Char('"'))) {
2169 qml_link.remove(0, 8).chop(1);
2170 }
2171 tag.clear();
2172 }
2173 } else {
2174 if (!inside_tag)
2175 qml_text += ch;
2176 else
2177 tag += ch;
2178 }
2179 }
2180
2181 if (!qml_link.isEmpty())
2182 setLink(qml_link);
2183 setUserInput(qml_text);
2184 setValue(Value(qml_text));
2185 } else {
2186 bool newStyleLoading = true;
2187 QString dataType = _dataType;
2188
2189 if (dataType.isNull()) {
2190 if (text.hasAttribute("dataType")) { // new docs
2191 dataType = text.attribute("dataType");
2192 } else { // old docs: do the ugly solution of parsing the text
2193 // ...except for date/time
2194 if (isDate() && (t.count('/') == 2))
2195 dataType = "Date";
2196 else if (isTime() && (t.count(':') == 2))
2197 dataType = "Time";
2198 else {
2199 parseUserInput(pasteOperation(t, userInput(), op));
2200 newStyleLoading = false;
2201 }
2202 }
2203 }
2204
2205 if (newStyleLoading) {
2206 // boolean ?
2207 if (dataType == "Bool")
2208 setValue(Value(t.toLower() == "true"));
2209
2210 // number ?
2211 else if (dataType == "Num") {
2212 bool ok = false;
2213 if (t.contains('.'))
2214 setValue(Value(t.toDouble(&ok))); // We save in non-localized format
2215 else
2216 setValue(Value(t.toLongLong(&ok)));
2217 if (!ok) {
2218 kWarning(36001) << "Couldn't parse '" << t << "' as number.";
2219 }
2220 /* We will need to localize the text version of the number */
2221 KLocale* locale = sheet()->map()->calculationSettings()->locale();
2222
2223 /* KLocale::formatNumber requires the precision we want to return.
2224 */
2225 int precision = t.length() - t.indexOf('.') - 1;
2226
2227 if (style().formatType() == Format::Percentage) {
2228 if (value().isInteger())
2229 t = locale->formatNumber(value().asInteger() * 100);
2230 else
2231 t = locale->formatNumber(numToDouble(value().asFloat() * 100.0), precision);
2232 setUserInput(pasteOperation(t, userInput(), op));
2233 setUserInput(userInput() + '%');
2234 } else {
2235 if (value().isInteger())
2236 t = locale->formatLong(value().asInteger());
2237 else
2238 t = locale->formatNumber(numToDouble(value().asFloat()), precision);
2239 setUserInput(pasteOperation(t, userInput(), op));
2240 }
2241 }
2242
2243 // date ?
2244 else if (dataType == "Date") {
2245 int pos = t.indexOf('/');
2246 int year = t.mid(0, pos).toInt();
2247 int pos1 = t.indexOf('/', pos + 1);
2248 int month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt();
2249 int day = t.right(t.length() - pos1 - 1).toInt();
2250 setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings()));
2251 if (value().asDate(sheet()->map()->calculationSettings()).isValid()) // Should always be the case for new docs
2252 setUserInput(locale()->formatDate(value().asDate(sheet()->map()->calculationSettings()), KLocale::ShortDate));
2253 else { // This happens with old docs, when format is set wrongly to date
2254 parseUserInput(pasteOperation(t, userInput(), op));
2255 }
2256 }
2257
2258 // time ?
2259 else if (dataType == "Time") {
2260 int hours = -1;
2261 int minutes = -1;
2262 int second = -1;
2263 int pos, pos1;
2264 pos = t.indexOf(':');
2265 hours = t.mid(0, pos).toInt();
2266 pos1 = t.indexOf(':', pos + 1);
2267 minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt();
2268 second = t.right(t.length() - pos1 - 1).toInt();
2269 setValue(Value(QTime(hours, minutes, second), sheet()->map()->calculationSettings()));
2270 if (value().asTime(sheet()->map()->calculationSettings()).isValid()) // Should always be the case for new docs
2271 setUserInput(locale()->formatTime(value().asTime(sheet()->map()->calculationSettings()), true));
2272 else { // This happens with old docs, when format is set wrongly to time
2273 parseUserInput(pasteOperation(t, userInput(), op));
2274 }
2275 }
2276
2277 else {
2278 // Set the cell's text
2279 setUserInput(pasteOperation(t, userInput(), op));
2280 setValue(Value(userInput()));
2281 }
2282 }
2283 }
2284
2285 if (!sheet()->isLoading())
2286 parseUserInput(userInput());
2287
2288 return true;
2289}
2290
2291QTime Cell::toTime(const KoXmlElement &element)
2292{
2293 //TODO: can't we use tryParseTime (after modification) instead?
2294 QString t = element.text();
2295 t = t.trimmed();
2296 int hours = -1;
2297 int minutes = -1;
2298 int second = -1;
2299 int pos, pos1;
2300 pos = t.indexOf(':');
2301 hours = t.mid(0, pos).toInt();
2302 pos1 = t.indexOf(':', pos + 1);
2303 minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt();
2304 second = t.right(t.length() - pos1 - 1).toInt();
2305 setValue(Value(QTime(hours, minutes, second), sheet()->map()->calculationSettings()));
2306 return value().asTime(sheet()->map()->calculationSettings());
2307}
2308
2309QDate Cell::toDate(const KoXmlElement &element)
2310{
2311 QString t = element.text();
2312 int pos;
2313 int pos1;
2314 int year = -1;
2315 int month = -1;
2316 int day = -1;
2317 pos = t.indexOf('/');
2318 year = t.mid(0, pos).toInt();
2319 pos1 = t.indexOf('/', pos + 1);
2320 month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt();
2321 day = t.right(t.length() - pos1 - 1).toInt();
2322 setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings()));
2323 return value().asDate(sheet()->map()->calculationSettings());
2324}
2325
2326QString Cell::pasteOperation(const QString &new_text, const QString &old_text, Paste::Operation op)
2327{
2328 if (op == Paste::OverWrite)
2329 return new_text;
2330
2331 QString tmp_op;
2332 QString tmp;
2333 QString old;
2334
2335 if (!new_text.isEmpty() && new_text[0] == '=') {
2336 tmp = new_text.right(new_text.length() - 1);
2337 } else {
2338 tmp = new_text;
2339 }
2340
2341 if (old_text.isEmpty() &&
2342 (op == Paste::Add || op == Paste::Mul || op == Paste::Sub || op == Paste::Div)) {
2343 old = "=0";
2344 }
2345
2346 if (!old_text.isEmpty() && old_text[0] == '=') {
2347 old = old_text.right(old_text.length() - 1);
2348 } else {
2349 old = old_text;
2350 }
2351
2352 bool b1, b2;
2353 tmp.toDouble(&b1);
2354 old.toDouble(&b2);
2355 if (b1 && !b2 && old.length() == 0) {
2356 old = '0';
2357 b2 = true;
2358 }
2359
2360 if (b1 && b2) {
2361 switch (op) {
2362 case Paste::Add:
2363 tmp_op = QString::number(old.toDouble() + tmp.toDouble());
2364 break;
2365 case Paste::Mul :
2366 tmp_op = QString::number(old.toDouble() * tmp.toDouble());
2367 break;
2368 case Paste::Sub:
2369 tmp_op = QString::number(old.toDouble() - tmp.toDouble());
2370 break;
2371 case Paste::Div:
2372 tmp_op = QString::number(old.toDouble() / tmp.toDouble());
2373 break;
2374 default:
2375 Q_ASSERT(0);
2376 }
2377
2378 return tmp_op;
2379 } else if ((new_text[0] == '=' && old_text[0] == '=') ||
2380 (b1 && old_text[0] == '=') || (new_text[0] == '=' && b2)) {
2381 switch (op) {
2382 case Paste::Add :
2383 tmp_op = "=(" + old + ")+" + '(' + tmp + ')';
2384 break;
2385 case Paste::Mul :
2386 tmp_op = "=(" + old + ")*" + '(' + tmp + ')';
2387 break;
2388 case Paste::Sub:
2389 tmp_op = "=(" + old + ")-" + '(' + tmp + ')';
2390 break;
2391 case Paste::Div:
2392 tmp_op = "=(" + old + ")/" + '(' + tmp + ')';
2393 break;
2394 default :
2395 Q_ASSERT(0);
2396 }
2397
2398 tmp_op = decodeFormula(tmp_op);
2399 return tmp_op;
2400 }
2401
2402 tmp = decodeFormula(new_text);
2403 return tmp;
2404}
2405
2406Cell& Cell::operator=(const Cell & other)
2407{
2408 d = other.d;
2409 return *this;
2410}
2411
2412bool Cell::operator<(const Cell& other) const
2413{
2414 if (sheet() != other.sheet())
2415 return sheet() < other.sheet(); // pointers!
2416 if (row() < other.row())
2417 return true;
2418 return ((row() == other.row()) && (column() < other.column()));
2419}
2420
2421bool Cell::operator==(const Cell& other) const
2422{
2423 return (row() == other.row() && column() == other.column() && sheet() == other.sheet());
2424}
2425
2426bool Cell::operator!() const
2427{
2428 return (!d); // isNull()
2429}
2430
2431bool Cell::compareData(const Cell& other) const
2432{
2433 if (value() != other.value())
2434 return false;
2435 if (formula() != other.formula())
2436 return false;
2437 if (link() != other.link())
2438 return false;
2439 if (mergedXCells() != other.mergedXCells())
2440 return false;
2441 if (mergedYCells() != other.mergedYCells())
2442 return false;
2443 if (style() != other.style())
2444 return false;
2445 if (comment() != other.comment())
2446 return false;
2447 if (conditions() != other.conditions())
2448 return false;
2449 if (validity() != other.validity())
2450 return false;
2451 return true;
2452}
2453
2454QPoint Cell::cellPosition() const
2455{
2456 Q_ASSERT(!isNull());
2457 return QPoint(column(), row());
2458}
2459