1/* This file is part of the KDE project
2 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 Copyright 1998, 1999 Torben Weis <weis@kde.org>
4 Copyright 1999- 2006 The KSpread Team <calligra-devel@kde.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21
22// Local
23#include "Condition.h"
24
25#include <float.h>
26
27#include "CalculationSettings.h"
28#include "Cell.h"
29#include "Formula.h"
30#include "Map.h"
31#include "NamedAreaManager.h"
32#include "Region.h"
33#include "Sheet.h"
34#include "Style.h"
35#include "StyleManager.h"
36#include "Util.h"
37#include "ValueCalc.h"
38#include "ValueConverter.h"
39#include "ValueParser.h"
40
41#include <KoGenStyles.h>
42
43#include <KoXmlWriter.h>
44#include <KoXmlNS.h>
45#include <kdebug.h>
46#include <QDomDocument>
47
48using namespace Calligra::Sheets;
49
50/////////////////////////////////////////////////////////////////////////////
51//
52// Conditional
53//
54/////////////////////////////////////////////////////////////////////////////
55
56Conditional::Conditional()
57 : cond(None)
58{
59}
60
61bool Conditional::operator==(const Conditional &other) const
62{
63 if (cond != other.cond) {
64 return false;
65 }
66 if (!value1.equal(other.value1)) {
67 return false;
68 }
69 if (!value2.equal(other.value2)) {
70 return false;
71 }
72 return styleName == other.styleName;
73}
74/////////////////////////////////////////////////////////////////////////////
75//
76// Conditions
77//
78/////////////////////////////////////////////////////////////////////////////
79
80class Conditions::Private : public QSharedData
81{
82public:
83 QLinkedList<Conditional> conditionList;
84 Style defaultStyle;
85};
86
87Conditions::Conditions()
88 : d(new Private)
89{
90}
91
92Conditions::Conditions(const Conditions& other)
93 : d(other.d)
94{
95}
96
97Conditions::~Conditions()
98{
99}
100
101bool Conditions::isEmpty() const
102{
103 return d->conditionList.isEmpty();
104}
105
106Style Conditions::testConditions( const Cell& cell ) const
107{
108 Conditional condition;
109 if (currentCondition(cell, condition)) {
110 StyleManager *const styleManager = cell.sheet()->map()->styleManager();
111 Style *const style = styleManager->style(condition.styleName);
112 if (style)
113 return *style;
114 }
115 return d->defaultStyle;
116}
117
118bool Conditions::currentCondition(const Cell& cell, Conditional & condition) const
119{
120 /* for now, the first condition that is true is the one that will be used */
121
122 const Value value = cell.value();
123 ValueCalc *const calc = cell.sheet()->map()->calc();
124
125 QLinkedList<Conditional>::const_iterator it;
126 for (it = d->conditionList.begin(); it != d->conditionList.end(); ++it) {
127 condition = *it;
128// kDebug() << "Checking condition resulting in applying" << it->styleName;
129
130 // The first value of the condition is always used and has to be
131 // comparable to the cell's value.
132 if (!value.allowComparison(condition.value1)) {
133 continue;
134 }
135
136 switch (condition.cond) {
137 case Conditional::Equal:
138 if (value.equal(condition.value1, calc->settings()->caseSensitiveComparisons())) {
139 return true;
140 }
141 break;
142 case Conditional::Superior:
143 if (value.greater(condition.value1, calc->settings()->caseSensitiveComparisons())) {
144 return true;
145 }
146 break;
147 case Conditional::Inferior:
148 if (value.less(condition.value1, calc->settings()->caseSensitiveComparisons())) {
149 return true;
150 }
151 break;
152 case Conditional::SuperiorEqual:
153 if (value.compare(condition.value1, calc->settings()->caseSensitiveComparisons()) >= 0) {
154 return true;
155 }
156 break;
157 case Conditional::InferiorEqual:
158 if (value.compare(condition.value1, calc->settings()->caseSensitiveComparisons()) <= 0) {
159 return true;
160 }
161 break;
162 case Conditional::Between: {
163 const QVector<Value> values(QVector<Value>() << condition.value1 << condition.value2);
164 const Value min = calc->min(values);
165 const Value max = calc->max(values);
166 if (value.compare(min, calc->settings()->caseSensitiveComparisons()) >= 0
167 && value.compare(max, calc->settings()->caseSensitiveComparisons()) <= 0) {
168 return true;
169 }
170 break;
171 }
172 case Conditional::Different: {
173 const QVector<Value> values(QVector<Value>() << condition.value1 << condition.value2);
174 const Value min = calc->min(values);
175 const Value max = calc->max(values);
176 if (value.greater(max, calc->settings()->caseSensitiveComparisons())
177 || value.less(min, calc->settings()->caseSensitiveComparisons())) {
178 return true;
179 }
180 break;
181 }
182 case Conditional::DifferentTo:
183 if (!value.equal(condition.value1, calc->settings()->caseSensitiveComparisons())) {
184 return true;
185 }
186 break;
187 case Conditional::IsTrueFormula:
188 // TODO: do some caching
189 if (isTrueFormula(cell, condition.value1.asString(), condition.baseCellAddress)) {
190 return true;
191 }
192 break;
193 default:
194 break;
195 }
196 }
197 return false;
198}
199
200bool Conditions::isTrueFormula(const Cell &cell, const QString &formula, const QString &baseCellAddress) const
201{
202 Map* const map = cell.sheet()->map();
203 ValueCalc *const calc = map->calc();
204 Formula f(cell.sheet(), cell);
205 f.setExpression('=' + formula);
206 Region r(baseCellAddress, map, cell.sheet());
207 if (r.isValid() && r.isSingular()) {
208 QPoint basePoint = static_cast<Region::Point*>(*r.constBegin())->pos();
209 QString newFormula('=');
210 const Tokens tokens = f.tokens();
211 for (int t = 0; t < tokens.count(); ++t) {
212 const Token token = tokens[t];
213 if (token.type() == Token::Cell || token.type() == Token::Range) {
214 if (map->namedAreaManager()->contains(token.text())) {
215 newFormula.append(token.text());
216 continue;
217 }
218 const Region region(token.text(), map, cell.sheet());
219 if (!region.isValid() || !region.isContiguous()) {
220 newFormula.append(token.text());
221 continue;
222 }
223 if (region.firstSheet() != r.firstSheet()) {
224 newFormula.append(token.text());
225 continue;
226 }
227 Region::Element* element = *region.constBegin();
228 if (element->type() == Region::Element::Point) {
229 Region::Point* point = static_cast<Region::Point*>(element);
230 QPoint pos = point->pos();
231 if (!point->isRowFixed()) {
232 int delta = pos.y() - basePoint.y();
233 pos.setY(cell.row() + delta);
234 }
235 if (!point->isColumnFixed()) {
236 int delta = pos.x() - basePoint.x();
237 pos.setX(cell.column() + delta);
238 }
239 newFormula.append(Region(pos, cell.sheet()).name());
240 } else {
241 Region::Range* range = static_cast<Region::Range*>(element);
242 QRect r = range->rect();
243 if (!range->isTopFixed()) {
244 int delta = r.top() - basePoint.y();
245 r.setTop(cell.row() + delta);
246 }
247 if (!range->isBottomFixed()) {
248 int delta = r.bottom() - basePoint.y();
249 r.setBottom(cell.row() + delta);
250 }
251 if (!range->isLeftFixed()) {
252 int delta = r.left() - basePoint.x();
253 r.setLeft(cell.column() + delta);
254 }
255 if (!range->isRightFixed()) {
256 int delta = r.right() - basePoint.x();
257 r.setRight(cell.column() + delta);
258 }
259 newFormula.append(Region(r, cell.sheet()).name());
260 }
261 } else {
262 newFormula.append(token.text());
263 }
264 }
265 f.setExpression(newFormula);
266 }
267 Value val = f.eval();
268 return calc->conv()->asBoolean(val).asBoolean();
269}
270
271QLinkedList<Conditional> Conditions::conditionList() const
272{
273 return d->conditionList;
274}
275
276void Conditions::setConditionList(const QLinkedList<Conditional> & list)
277{
278 d->conditionList = list;
279}
280
281Style Conditions::defaultStyle() const
282{
283 return d->defaultStyle;
284}
285
286void Conditions::setDefaultStyle(const Style &style)
287{
288 d->defaultStyle = style;
289}
290
291void Conditions::saveOdfConditions(KoGenStyle &currentCellStyle, ValueConverter *converter) const
292{
293 //todo fix me with kspread old format!!!
294 if (d->conditionList.isEmpty())
295 return;
296 QLinkedList<Conditional>::const_iterator it;
297 int i = 0;
298 for (it = d->conditionList.begin(); it != d->conditionList.end(); ++it, ++i) {
299 Conditional condition = *it;
300 //<style:map style:condition="cell-content()=45" style:apply-style-name="Default" style:base-cell-address="Sheet1.E10"/>
301 QMap<QString, QString> map;
302 map.insert("style:condition", saveOdfConditionValue(condition, converter));
303 map.insert("style:apply-style-name", condition.styleName);
304 if (!condition.baseCellAddress.isEmpty())
305 map.insert("style:base-cell-address", condition.baseCellAddress);
306 currentCellStyle.addStyleMap(map);
307 }
308}
309
310QString Conditions::saveOdfConditionValue(const Conditional &condition, ValueConverter* converter) const
311{
312 //we can also compare text value.
313 //todo adapt it.
314 QString value;
315 switch (condition.cond) {
316 case Conditional::None:
317 break;
318 case Conditional::Equal:
319 value = "cell-content()=" + converter->asString(condition.value1).asStringWithDoubleQuotes();
320 break;
321 case Conditional::Superior:
322 value = "cell-content()>" + converter->asString(condition.value1).asStringWithDoubleQuotes();
323 break;
324 case Conditional::Inferior:
325 value = "cell-content()<" + converter->asString(condition.value1).asStringWithDoubleQuotes();
326 break;
327 case Conditional::SuperiorEqual:
328 value = "cell-content()>=" + converter->asString(condition.value1).asStringWithDoubleQuotes();
329 break;
330 case Conditional::InferiorEqual:
331 value = "cell-content()<=" + converter->asString(condition.value1).asStringWithDoubleQuotes();
332 break;
333 case Conditional::Between:
334 value = "cell-content-is-between(" +
335 converter->asString(condition.value1).asStringWithDoubleQuotes() +
336 ',' +
337 converter->asString(condition.value2).asStringWithDoubleQuotes() +
338 ')';
339 break;
340 case Conditional::DifferentTo:
341 value = "cell-content()!=" + converter->asString(condition.value1).asStringWithDoubleQuotes();
342 break;
343 case Conditional::Different:
344 value = "cell-content-is-not-between(" +
345 converter->asString(condition.value1).asStringWithDoubleQuotes() +
346 ',' +
347 converter->asString(condition.value2).asStringWithDoubleQuotes() +
348 ')';
349 break;
350 case Conditional::IsTrueFormula:
351 value = "is-true-formula(" +
352 Odf::encodeFormula(condition.value1.asString()) +
353 ')';
354 }
355 return value;
356}
357
358
359QDomElement Conditions::saveConditions(QDomDocument &doc, ValueConverter *converter) const
360{
361 QDomElement conditions = doc.createElement("condition");
362 QLinkedList<Conditional>::const_iterator it;
363 QDomElement child;
364 int num = 0;
365 QString name;
366
367 for (it = d->conditionList.begin(); it != d->conditionList.end(); ++it) {
368 Conditional condition = *it;
369
370 /* the name of the element will be "condition<n>"
371 * This is unimportant now but in older versions three conditions were
372 * hardcoded with names "first" "second" and "third"
373 */
374 name.setNum(num);
375 name.prepend("condition");
376
377 child = doc.createElement(name);
378 child.setAttribute("cond", (int) condition.cond);
379
380 // TODO: saving in KSpread 1.1 | KSpread 1.2 format
381 if (condition.value1.isString()) {
382 child.setAttribute("strval1", condition.value1.asString());
383 if (!condition.value2.asString().isEmpty()) {
384 child.setAttribute("strval2", condition.value2.asString());
385 }
386 } else {
387 child.setAttribute("val1", converter->asString(condition.value1).asString());
388 child.setAttribute("val2", converter->asString(condition.value2).asString());
389 }
390 if (!condition.styleName.isEmpty()) {
391 child.setAttribute("style", condition.styleName);
392 }
393
394 conditions.appendChild(child);
395
396 ++num;
397 }
398
399 if (num == 0) {
400 /* there weren't any real conditions -- return a null dom element */
401 return QDomElement();
402 } else {
403 return conditions;
404 }
405}
406
407Conditional Conditions::loadOdfCondition(const QString &conditionValue, const QString &applyStyleName,
408 const QString& baseCellAddress, const ValueParser *parser)
409{
410 //kDebug(36003) << "\tcondition:" << conditionValue;
411 Conditional newCondition;
412 loadOdfConditionValue(conditionValue, newCondition, parser);
413 if (!applyStyleName.isNull()) {
414 //kDebug(36003) << "\tstyle:" << applyStyleName;
415 newCondition.styleName = applyStyleName;
416 }
417 newCondition.baseCellAddress = baseCellAddress;
418 d->conditionList.append(newCondition);
419 return newCondition;
420}
421
422void Conditions::loadOdfConditions(const KoXmlElement &element, const ValueParser *parser, const StyleManager *styleManager)
423{
424 kDebug(36003) << "Loading conditional styles";
425 KoXmlNode node(element);
426
427 while (!node.isNull()) {
428 KoXmlElement elementItem = node.toElement();
429 if (elementItem.tagName() == "map" && elementItem.namespaceURI() == KoXmlNS::style) {
430 QString conditionValue = elementItem.attributeNS(KoXmlNS::style, "condition", QString());
431 QString applyStyleName;
432 if (elementItem.hasAttributeNS(KoXmlNS::style, "apply-style-name"))
433 applyStyleName = elementItem.attributeNS(KoXmlNS::style, "apply-style-name", QString());
434 if (!applyStyleName.isEmpty() && styleManager) {
435 QString odfStyle = styleManager->openDocumentName(applyStyleName);
436 if (!odfStyle.isEmpty()) applyStyleName = odfStyle;
437 }
438 QString baseCellAddress = elementItem.attributeNS(KoXmlNS::style, "base-cell-address");
439 loadOdfCondition(conditionValue, applyStyleName, baseCellAddress, parser);
440 }
441 node = node.nextSibling();
442 }
443}
444
445void Conditions::loadOdfConditionValue(const QString &styleCondition, Conditional &newCondition, const ValueParser *parser)
446{
447 QString val(styleCondition);
448 if (val.contains("cell-content()")) {
449 val.remove("cell-content()");
450 loadOdfCondition(val, newCondition, parser);
451 } else if (val.contains("value()")) {
452 val.remove("value()");
453 loadOdfCondition(val, newCondition, parser);
454 }
455
456 //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value)
457 //for the moment we support just int/double value, not text/date/time :(
458 if (val.contains("cell-content-is-between(")) {
459 val.remove("cell-content-is-between(");
460 val.remove(')');
461 QStringList listVal = val.split(',', QString::SkipEmptyParts);
462 loadOdfValidationValue(listVal, newCondition, parser);
463 newCondition.cond = Conditional::Between;
464 } else if (val.contains("cell-content-is-not-between(")) {
465 val.remove("cell-content-is-not-between(");
466 val.remove(')');
467 QStringList listVal = val.split(',', QString::SkipEmptyParts);
468 loadOdfValidationValue(listVal, newCondition, parser);
469 newCondition.cond = Conditional::Different;
470 } else if (val.startsWith(QLatin1String("is-true-formula("))) {
471 val = val.mid(16);
472 if (val.endsWith(QLatin1Char(')'))) val.chop(1);
473 newCondition.cond = Conditional::IsTrueFormula;
474 newCondition.value1 = Value(Odf::decodeFormula(val));
475 }
476}
477
478void Conditions::loadOdfCondition(QString &valExpression, Conditional &newCondition, const ValueParser *parser)
479{
480 QString value;
481 if (valExpression.indexOf("<=") == 0) {
482 value = valExpression.remove(0, 2);
483 newCondition.cond = Conditional::InferiorEqual;
484 } else if (valExpression.indexOf(">=") == 0) {
485 value = valExpression.remove(0, 2);
486 newCondition.cond = Conditional::SuperiorEqual;
487 } else if (valExpression.indexOf("!=") == 0) {
488 //add Differentto attribute
489 value = valExpression.remove(0, 2);
490 newCondition.cond = Conditional::DifferentTo;
491 } else if (valExpression.indexOf('<') == 0) {
492 value = valExpression.remove(0, 1);
493 newCondition.cond = Conditional::Inferior;
494 } else if (valExpression.indexOf('>') == 0) {
495 value = valExpression.remove(0, 1);
496 newCondition.cond = Conditional::Superior;
497 } else if (valExpression.indexOf('=') == 0) {
498 value = valExpression.remove(0, 1);
499 newCondition.cond = Conditional::Equal;
500 } else {
501 kWarning() << " I don't know how to parse it :" << valExpression;
502 }
503 //kDebug(36003) << "\tvalue:" << value;
504
505 if (value.length() > 1 && value[0] == '"' && value[value.length()-1] == '"') {
506 newCondition.value1 = Value(value.mid(1, value.length()-2));
507 } else {
508 newCondition.value1 = parser->parse(value);
509 }
510}
511
512void Conditions::loadOdfValidationValue(const QStringList &listVal, Conditional &newCondition, const ValueParser *parser)
513{
514 kDebug(36003) << " listVal[0] :" << listVal[0] << " listVal[1] :" << listVal[1];
515 newCondition.value1 = parser->parse(listVal[0]);
516 newCondition.value2 = parser->parse(listVal[1]);
517}
518
519void Conditions::loadConditions(const KoXmlElement &element, const ValueParser *parser)
520{
521 Conditional newCondition;
522
523 KoXmlElement conditionElement;
524 forEachElement(conditionElement, element) {
525 if (!conditionElement.hasAttribute("cond"))
526 continue;
527
528 bool ok = true;
529 newCondition.cond = (Conditional::Type) conditionElement.attribute("cond").toInt(&ok);
530 if(!ok)
531 continue;
532
533 if (conditionElement.hasAttribute("val1")) {
534 newCondition.value1 = parser->parse(conditionElement.attribute("val1"));
535
536 if (conditionElement.hasAttribute("val2"))
537 newCondition.value2 = parser->parse(conditionElement.attribute("val2"));
538 }
539
540 if (conditionElement.hasAttribute("strval1")) {
541 newCondition.value1 = Value(conditionElement.attribute("strval1"));
542
543 if (conditionElement.hasAttribute("strval2"))
544 newCondition.value2 = Value(conditionElement.attribute("strval2"));
545 }
546
547 if (conditionElement.hasAttribute("style")) {
548 newCondition.styleName = conditionElement.attribute("style");
549 }
550
551 d->conditionList.append(newCondition);
552 }
553}
554
555void Conditions::operator=(const Conditions & other)
556{
557 d = other.d;
558}
559
560bool Conditions::operator==(const Conditions& other) const
561{
562 if (d->conditionList.count() != other.d->conditionList.count())
563 return false;
564 QLinkedList<Conditional>::ConstIterator end(d->conditionList.end());
565 for (QLinkedList<Conditional>::ConstIterator it(d->conditionList.begin()); it != end; ++it) {
566 bool found = false;
567 QLinkedList<Conditional>::ConstIterator otherEnd(other.d->conditionList.end());
568 for (QLinkedList<Conditional>::ConstIterator otherIt(other.d->conditionList.begin()); otherIt != otherEnd; ++otherIt) {
569 if ((*it) == (*otherIt))
570 found = true;
571 }
572 if (!found)
573 return false;
574 }
575 return true;
576}
577
578uint Calligra::Sheets::qHash(const Conditions &c)
579{
580 uint res = 0;
581 foreach (const Conditional& co, c.conditionList()) {
582 res ^= qHash(co);
583 }
584 return res;
585}
586
587uint Calligra::Sheets::qHash(const Conditional& c)
588{
589 return qHash(c.value1);
590}
591