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 | |
48 | using namespace Calligra::Sheets; |
49 | |
50 | ///////////////////////////////////////////////////////////////////////////// |
51 | // |
52 | // Conditional |
53 | // |
54 | ///////////////////////////////////////////////////////////////////////////// |
55 | |
56 | Conditional::Conditional() |
57 | : cond(None) |
58 | { |
59 | } |
60 | |
61 | bool 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 | |
80 | class Conditions::Private : public QSharedData |
81 | { |
82 | public: |
83 | QLinkedList<Conditional> conditionList; |
84 | Style defaultStyle; |
85 | }; |
86 | |
87 | Conditions::Conditions() |
88 | : d(new Private) |
89 | { |
90 | } |
91 | |
92 | Conditions::Conditions(const Conditions& other) |
93 | : d(other.d) |
94 | { |
95 | } |
96 | |
97 | Conditions::~Conditions() |
98 | { |
99 | } |
100 | |
101 | bool Conditions::isEmpty() const |
102 | { |
103 | return d->conditionList.isEmpty(); |
104 | } |
105 | |
106 | Style 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 | |
118 | bool 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 | |
200 | bool 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 | |
271 | QLinkedList<Conditional> Conditions::conditionList() const |
272 | { |
273 | return d->conditionList; |
274 | } |
275 | |
276 | void Conditions::setConditionList(const QLinkedList<Conditional> & list) |
277 | { |
278 | d->conditionList = list; |
279 | } |
280 | |
281 | Style Conditions::defaultStyle() const |
282 | { |
283 | return d->defaultStyle; |
284 | } |
285 | |
286 | void Conditions::setDefaultStyle(const Style &style) |
287 | { |
288 | d->defaultStyle = style; |
289 | } |
290 | |
291 | void Conditions::saveOdfConditions(KoGenStyle ¤tCellStyle, 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 | |
310 | QString 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 | |
359 | QDomElement 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 | |
407 | Conditional 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 | |
422 | void 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 | |
445 | void 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 | |
478 | void 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 | |
512 | void 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 | |
519 | void 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 | |
555 | void Conditions::operator=(const Conditions & other) |
556 | { |
557 | d = other.d; |
558 | } |
559 | |
560 | bool 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 | |
578 | uint 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 | |
587 | uint Calligra::Sheets::qHash(const Conditional& c) |
588 | { |
589 | return qHash(c.value1); |
590 | } |
591 | |