kspread

valueformatter.cpp
1/* This file is part of the KDE project
2 Copyright 2004 Tomas Mecir <mecirt@gmail.com>
3 Copyright (C) 1998-2004 KSpread Team <koffice-devel@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include "valueformatter.h"
22
23#include "kspread_cell.h"
24#include "kspread_locale.h"
25#include "kspread_util.h"
26#include "valueconverter.h"
27
28#include <kcalendarsystem.h>
29#include <kdebug.h>
30#include <tdelocale.h>
31
32#include <float.h>
33#include <math.h>
34
35using namespace KSpread;
36
38{
39}
40
41TQString ValueFormatter::formatText (Cell *cell, FormatType fmtType)
42{
43 if (cell->hasError ())
44 return errorFormat (cell);
45
46 TQString str;
47
48 Format::FloatFormat floatFormat =
49 cell->format()->floatFormat (cell->column(), cell->row());
50 int precision = cell->format()->precision (cell->column(), cell->row());
51 TQString prefix = cell->format()->prefix (cell->column(), cell->row());
52 TQString postfix = cell->format()->postfix (cell->column(), cell->row());
53 Format::Currency currency;
54 bool valid = cell->format()->currencyInfo(currency);
55 TQString currencySymbol = valid ? currency.symbol : TQString();
56
57 return formatText (cell->value(), fmtType, precision,
58 floatFormat, prefix, postfix, currencySymbol);
59}
60
61TQString ValueFormatter::formatText (const Value &value,
62 FormatType fmtType, int precision, Format::FloatFormat floatFormat,
63 const TQString &prefix, const TQString &postfix, const TQString &currencySymbol)
64{
65 //if we have an array, use its first element
66 if (value.isArray())
67 return formatText (value.element (0, 0), fmtType, precision,
68 floatFormat, prefix, postfix, currencySymbol);
69
70 TQString str;
71
72 //step 1: determine formatting that will be used
73 fmtType = determineFormatting (value, fmtType);
74
75 //step 2: format the value !
76
77 //text
78 if (fmtType == Text_format)
79 {
80 str = converter->asString (value).asString();
81 if (!str.isEmpty() && str[0]=='\'' )
82 str = str.mid(1);
83 }
84
85 //date
86 else if (formatIsDate (fmtType))
87 str = dateFormat (value.asDate(), fmtType);
88
89 //time
90 else if (formatIsTime (fmtType))
91 str = timeFormat (value.asDateTime(), fmtType);
92
93 //fraction
94 else if (formatIsFraction (fmtType))
95 str = fractionFormat (value.asFloat(), fmtType);
96
97 //another
98 else
99 {
100 //some cell parameters ...
101 double v = converter->asFloat (value).asFloat();
102
103 // Always unsigned ?
104 if ((floatFormat == Format::AlwaysUnsigned) && (v < 0.0))
105 v *= -1.0;
106
107 // Make a string out of it.
108 str = createNumberFormat (v, precision, fmtType,
109 (floatFormat == Format::AlwaysSigned), currencySymbol);
110
111 // Remove trailing zeros and the decimal point if necessary
112 // unless the number has no decimal point
113 if (precision == -1)
114 {
115 TQChar decimal_point = converter->locale()->decimalSymbol()[0];
116 if ( decimal_point.isNull() )
117 decimal_point = '.';
118
119 removeTrailingZeros (str, decimal_point);
120 }
121 }
122
123 if (!prefix.isEmpty())
124 str = prefix + ' ' + str;
125
126 if( !postfix.isEmpty())
127 str += ' ' + postfix;
128
129 //kdDebug() << "ValueFormatter says: " << str << endl;
130 return str;
131}
132
134 FormatType fmtType)
135{
136 //if the cell value is a string, then we want to display it as-is,
137 //no matter what, same if the cell is empty
138 if (value.isString () || (value.format() == Value::fmt_None))
139 return Text_format;
140 //same if we're supposed to display string, no matter what we actually got
141 if (fmtType == Text_format)
142 return Text_format;
143
144 //now, everything depends on whether the formatting is Generic or not
145 if (fmtType == Generic_format)
146 {
147 //here we decide based on value's format...
148 Value::Format fmt = value.format();
149 switch (fmt) {
150 case Value::fmt_None:
151 fmtType = Text_format;
152 break;
153 case Value::fmt_Boolean:
154 fmtType = Text_format;
155 break;
156 case Value::fmt_Number:
157 if (value.asFloat() > 1e+10)
158 fmtType = Scientific_format;
159 else
160 fmtType = Number_format;
161 break;
162 case Value::fmt_Percent:
163 fmtType = Percentage_format;
164 break;
165 case Value::fmt_Money:
166 fmtType = Money_format;
167 break;
168 case Value::fmt_DateTime:
169 fmtType = TextDate_format;
170 break;
171 case Value::fmt_Date:
172 fmtType = ShortDate_format;
173 break;
174 case Value::fmt_Time:
175 fmtType = Time_format;
176 break;
177 case Value::fmt_String:
178 //this should never happen
179 fmtType = Text_format;
180 break;
181 };
182 return fmtType;
183 }
184 else
185 {
186 //we'll mostly want to use the given formatting, the only exception
187 //being Boolean values
188
189 //TODO: is this correct? We may also want to convert bools to 1s and 0s
190 //if we want to display a number...
191
192 //TODO: what to do about Custom formatting? We don't support it as of now,
193 // but we'll have it ... one day, that is ...
194 if (value.isBoolean())
195 return Text_format;
196 else
197 return fmtType;
198 }
199}
200
201
202void ValueFormatter::removeTrailingZeros (TQString &str, TQChar decimal_point)
203{
204 if (str.find (decimal_point) < 0)
205 //no decimal point -> nothing to do
206 return;
207
208 int start = 0;
209 int cslen = converter->locale()->currencySymbol().length();
210 if (str.find ('%') != -1)
211 start = 2;
212 else if (str.find (converter->locale()->currencySymbol()) ==
213 ((int) (str.length() - cslen)))
214 start = cslen + 1;
215 else if ((start = str.find ('E')) != -1)
216 start = str.length() - start;
217 else
218 start = 0;
219
220 int i = str.length() - start;
221 bool bFinished = false;
222 while ( !bFinished && i > 0 )
223 {
224 TQChar ch = str[i - 1];
225 if (ch == '0')
226 str.remove (--i,1);
227 else
228 {
229 bFinished = true;
230 if (ch == decimal_point)
231 str.remove (--i, 1);
232 }
233 }
234}
235
236TQString ValueFormatter::createNumberFormat ( double value, int precision,
237 FormatType fmt, bool alwaysSigned, const TQString& currencySymbol)
238{
239 // if precision is -1, ask for a huge number of decimals, we'll remove
240 // the zeros later. Is 8 ok ?
241 // Stefan: No. Use maximum possible decimal precision (DBL_DIG) instead.
242 int p = (precision == -1) ? 8 : precision;
243 TQString localizedNumber;
244 int pos = 0;
245
246 //multiply value by 100 for percentage format
247 if (fmt == Percentage_format)
248 value *= 100;
249
250 // this will avoid displaying negative zero, i.e "-0.0000"
251 if( fabs( value ) < DBL_EPSILON ) value = 0.0;
252
253 // round the number, based on desired precision if not scientific is chosen
254 //(scientific has relative precision)
255 if( fmt != Scientific_format )
256 {
257 double m[] = { 1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10 };
258 double mm = (p > 10) ? pow(10.0,p) : m[p];
259 bool neg = value < 0;
260 value = floor( fabs(value)*mm + 0.5 ) / mm;
261 if( neg ) value = -value;
262 }
263
264 TQChar decimal_point;
265 switch (fmt)
266 {
267 case Number_format:
268 localizedNumber = converter->locale()->formatNumber(value, p);
269 break;
270 case Percentage_format:
271 localizedNumber = converter->locale()->formatNumber (value, p)+ " %";
272 break;
273 case Money_format:
274 localizedNumber = converter->locale()->formatMoney (value,
275 currencySymbol.isEmpty() ? converter->locale()->currencySymbol() : currencySymbol, p );
276 break;
277 case Scientific_format:
278 decimal_point = converter->locale()->decimalSymbol()[0];
279 localizedNumber = TQString::number (value, 'E', p);
280 if ((pos = localizedNumber.find ('.')) != -1)
281 localizedNumber = localizedNumber.replace (pos, 1, decimal_point);
282 break;
283 default :
284 //other formatting?
285 // This happens with Custom_format...
286 kdDebug(36001)<<"Wrong usage of ValueFormatter::createNumberFormat fmt=" << fmt << "\n";
287 break;
288 }
289
290 //prepend positive sign if needed
291 if (alwaysSigned && value >= 0 )
292 if (converter->locale()->positiveSign().isEmpty())
293 localizedNumber='+'+localizedNumber;
294
295 return localizedNumber;
296}
297
298TQString ValueFormatter::fractionFormat (double value, FormatType fmtType)
299{
300 double result = value - floor(value);
301 int index;
302 int limit = 0;
303
304 /* return w/o fraction part if not necessary */
305 if (result == 0)
306 return TQString::number(value);
307
308 switch (fmtType) {
309 case fraction_half:
310 index = 2;
311 break;
312 case fraction_quarter:
313 index = 4;
314 break;
315 case fraction_eighth:
316 index = 8;
317 break;
318 case fraction_sixteenth:
319 index = 16;
320 break;
321 case fraction_tenth:
322 index = 10;
323 break;
324 case fraction_hundredth:
325 index = 100;
326 break;
327 case fraction_one_digit:
328 index = 3;
329 limit = 9;
330 break;
331 case fraction_two_digits:
332 index = 4;
333 limit = 99;
334 break;
335 case fraction_three_digits:
336 index = 5;
337 limit = 999;
338 break;
339 default:
340 kdDebug(36001) << "Error in Fraction format\n";
341 return TQString::number(value);
342 break;
343 } /* switch */
344
345
346 /* handle halves, quarters, tenths, ... */
347
348 if (fmtType != fraction_three_digits
349 && fmtType != fraction_two_digits
350 && fmtType != fraction_one_digit) {
351 double calc = 0;
352 int index1 = 0;
353 double diff = result;
354 for (int i = 1; i <= index; i++) {
355 calc = i * 1.0 / index;
356 if (fabs(result - calc) < diff) {
357 index1 = i;
358 diff = fabs(result - calc);
359 }
360 }
361 if( index1 == 0 ) return TQString("%1").arg( floor(value) );
362 if( index1 == index ) return TQString("%1").arg( floor(value)+1 );
363 if( floor(value) == 0)
364 return TQString("%1/%2").arg( index1 ).arg( index );
365
366 return TQString("%1 %2/%3")
367 .arg( floor(value) )
368 .arg( index1 )
369 .arg( index );
370 }
371
372
373 /* handle fraction_one_digit, fraction_two_digit
374 * and fraction_three_digit style */
375
376 double precision, denominator, numerator;
377
378 do {
379 double val1 = result;
380 double val2 = rint(result);
381 double inter2 = 1;
382 double inter4, p, q;
383 inter4 = p = q = 0;
384
385 precision = pow(10.0, -index);
386 numerator = val2;
387 denominator = 1;
388
389 while (fabs(numerator/denominator - result) > precision) {
390 val1 = (1 / (val1 - val2));
391 val2 = rint(val1);
392 p = val2 * numerator + inter2;
393 q = val2 * denominator + inter4;
394 inter2 = numerator;
395 inter4 = denominator;
396 numerator = p;
397 denominator = q;
398 }
399 index--;
400 } while (fabs(denominator) > limit);
401
402 denominator = fabs(denominator);
403 numerator = fabs(numerator);
404
405 if (denominator == numerator)
406 return TQString().setNum(floor(value + 1));
407 else
408 {
409 if ( floor(value) == 0 )
410 return TQString("%1/%2").arg(numerator).arg(denominator);
411 else
412 return TQString("%1 %2/%3")
413 .arg(floor(value))
414 .arg(numerator)
415 .arg(denominator);
416 }
417}
418
419TQString ValueFormatter::timeFormat (const TQDateTime &dt, FormatType fmtType)
420{
421 if (fmtType == Time_format)
422 return converter->locale()->formatTime(dt.time(), false);
423
424 if (fmtType == SecondeTime_format)
425 return converter->locale()->formatTime(dt.time(), true);
426
427 int h = dt.time().hour();
428 int m = dt.time().minute();
429 int s = dt.time().second();
430
431 TQString hour = ( h < 10 ? "0" + TQString::number(h) : TQString::number(h) );
432 TQString minute = ( m < 10 ? "0" + TQString::number(m) : TQString::number(m) );
433 TQString second = ( s < 10 ? "0" + TQString::number(s) : TQString::number(s) );
434 bool pm = (h > 12);
435 TQString AMPM( pm ? i18n("PM"):i18n("AM") );
436
437 if (fmtType == Time_format1) { // 9 : 01 AM
438 return TQString("%1:%2 %3")
439 .arg((pm ? h - 12 : h),2)
440 .arg(minute,2)
441 .arg(AMPM);
442 }
443
444 if (fmtType == Time_format2) { //9:01:05 AM
445 return TQString("%1:%2:%3 %4")
446 .arg((pm ? h-12 : h),2)
447 .arg(minute,2)
448 .arg(second,2)
449 .arg(AMPM);
450 }
451
452 if (fmtType == Time_format3) {
453 return TQString("%1 %2 %3 %4 %5 %6") // 9 h 01 min 28 s
454 .arg(hour,2)
455 .arg(i18n("h"))
456 .arg(minute,2)
457 .arg(i18n("min"))
458 .arg(second,2)
459 .arg(i18n("s"));
460 }
461
462 if (fmtType == Time_format4) { // 9:01
463 return TQString("%1:%2").arg(hour, 2).arg(minute, 2);
464 }
465
466 if (fmtType == Time_format5) { // 9:01:12
467 return TQString("%1:%2:%3").arg(hour, 2).arg(minute, 2).arg(second, 2);
468 }
469
470 TQDate d1(dt.date());
471 TQDate d2( 1899, 12, 31 );
472 int d = d2.daysTo( d1 ) + 1;
473
474 h += d * 24;
475
476 if (fmtType == Time_format6)
477 { // [mm]:ss
478 m += (h * 60);
479 return TQString("%1:%2").arg(m, 1).arg(second, 2);
480 }
481 if (fmtType == Time_format7) { // [h]:mm:ss
482 return TQString("%1:%2:%3").arg(h, 1).arg(minute, 2).arg(second, 2);
483 }
484 if (fmtType == Time_format8)
485 { // [h]:mm
486 m += (h * 60);
487 return TQString("%1:%2").arg(h, 1).arg(minute, 2);
488 }
489
490 return converter->locale()->formatTime( dt.time(), false );
491}
492
493TQString ValueFormatter::dateFormat (const TQDate &date, FormatType fmtType)
494{
495 TQString tmp;
496 if (fmtType == ShortDate_format) {
497 tmp = converter->locale()->formatDate(date, true);
498 }
499 else if (fmtType == TextDate_format) {
500 tmp = converter->locale()->formatDate(date, false);
501 }
502 else if (fmtType == date_format1) { /*18-Feb-99 */
503 tmp = TQString().sprintf("%02d", date.day());
504 tmp += "-" + converter->locale()->calendar()->monthString(date, true) + "-";
505 tmp += TQString::number(date.year()).right(2);
506 }
507 else if (fmtType == date_format2) { /*18-Feb-1999 */
508 tmp = TQString().sprintf("%02d", date.day());
509 tmp += "-" + converter->locale()->calendar()->monthString(date, true) + "-";
510 tmp += TQString::number(date.year());
511 }
512 else if (fmtType == date_format3) { /*18-Feb */
513 tmp = TQString().sprintf("%02d", date.day());
514 tmp += "-" + converter->locale()->calendar()->monthString(date, true);
515 }
516 else if (fmtType == date_format4) { /*18-05 */
517 tmp = TQString().sprintf("%02d", date.day());
518 tmp += "-" + TQString().sprintf("%02d", date.month() );
519 }
520 else if (fmtType == date_format5) { /*18/05/00 */
521 tmp = TQString().sprintf("%02d", date.day());
522 tmp += "/" + TQString().sprintf("%02d", date.month()) + "/";
523 tmp += TQString::number(date.year()).right(2);
524 }
525 else if (fmtType == date_format6) { /*18/05/1999 */
526 tmp = TQString().sprintf("%02d", date.day());
527 tmp += "/" + TQString().sprintf("%02d", date.month()) + "/";
528 tmp += TQString::number(date.year());
529 }
530 else if (fmtType == date_format7) { /*Feb-99 */
531 tmp = converter->locale()->calendar()->monthString(date, true) + "-";
532 tmp += TQString::number(date.year()).right(2);
533 }
534 else if (fmtType == date_format8) { /*February-99 */
535 tmp = converter->locale()->calendar()->monthString(date, false) + "-";
536 tmp += TQString::number(date.year()).right(2);
537 }
538 else if (fmtType == date_format9) { /*February-1999 */
539 tmp = converter->locale()->calendar()->monthString(date, false) + "-";
540 tmp += TQString::number(date.year());
541 }
542 else if (fmtType == date_format10) { /*F-99 */
543 tmp = converter->locale()->calendar()->monthString(date, false).at(0) + "-";
544 tmp += TQString::number(date.year()).right(2);
545 }
546 else if (fmtType == date_format11) { /*18/Feb */
547 tmp = TQString().sprintf("%02d", date.day()) + "/";
548 tmp += converter->locale()->calendar()->monthString(date, true);
549 }
550 else if (fmtType == date_format12) { /*18/02 */
551 tmp = TQString().sprintf("%02d", date.day()) + "/";
552 tmp += TQString().sprintf("%02d", date.month());
553 }
554 else if (fmtType == date_format13) { /*18/Feb/1999 */
555 tmp = TQString().sprintf("%02d", date.day());
556 tmp += "/" + converter->locale()->calendar()->monthString(date, true) + "/";
557 tmp += TQString::number(date.year());
558 }
559 else if (fmtType == date_format14) { /*2000/Feb/18 */
560 tmp = TQString::number(date.year());
561 tmp += "/" + converter->locale()->calendar()->monthString(date, true) + "/";
562 tmp += TQString().sprintf("%02d", date.day());
563 }
564 else if (fmtType == date_format15) { /*2000-Feb-18 */
565 tmp = TQString::number(date.year());
566 tmp += "-" + converter->locale()->calendar()->monthString(date, true) + "-";
567 tmp += TQString().sprintf("%02d", date.day());
568 }
569 else if (fmtType == date_format16) { /*2000-02-18 */
570 tmp = TQString::number(date.year());
571 tmp += "-" + TQString().sprintf("%02d", date.month()) + "-";
572 tmp += TQString().sprintf("%02d", date.day());
573 }
574 else if (fmtType == date_format17) { /*2 february 2000 */
575 tmp = TQString().sprintf("%d", date.day());
576 tmp += " " + converter->locale()->calendar()->monthString(date, false) + " ";
577 tmp += TQString::number(date.year());
578 }
579 else if (fmtType == date_format18) { /*02/18/1999 */
580 tmp = TQString().sprintf("%02d", date.month());
581 tmp += "/" + TQString().sprintf("%02d", date.day());
582 tmp += "/" + TQString::number(date.year());
583 }
584 else if (fmtType == date_format19) { /*02/18/99 */
585 tmp = TQString().sprintf("%02d", date.month());
586 tmp += "/" + TQString().sprintf("%02d", date.day());
587 tmp += "/" + TQString::number(date.year()).right(2);
588 }
589 else if (fmtType == date_format20) { /*Feb/18/99 */
590 tmp = converter->locale()->calendar()->monthString(date, true);
591 tmp += "/" + TQString().sprintf("%02d", date.day());
592 tmp += "/" + TQString::number(date.year()).right(2);
593 }
594 else if (fmtType == date_format21) { /*Feb/18/1999 */
595 tmp = converter->locale()->calendar()->monthString(date, true);
596 tmp += "/" + TQString().sprintf("%02d", date.day());
597 tmp += "/" + TQString::number(date.year());
598 }
599 else if (fmtType == date_format22) { /*Feb-1999 */
600 tmp = converter->locale()->calendar()->monthString(date, true) + "-";
601 tmp += TQString::number(date.year());
602 }
603 else if (fmtType == date_format23) { /*1999 */
604 tmp = TQString::number(date.year());
605 }
606 else if (fmtType == date_format24) { /*99 */
607 tmp = TQString::number(date.year()).right(2);
608 }
609 else if (fmtType == date_format25) { /*2000/02/18 */
610 tmp = TQString::number(date.year());
611 tmp += "/" + TQString().sprintf("%02d", date.month());
612 tmp += "/" + TQString().sprintf("%02d", date.day());
613 }
614 else if (fmtType == date_format26) { /*2000/Feb/18 */
615 tmp = TQString::number(date.year());
616 tmp += "/" + converter->locale()->calendar()->monthString(date, true);
617 tmp += "/" + TQString().sprintf("%02d", date.day());
618 }
619 else
620 tmp = converter->locale()->formatDate(date, true);
621
622 // Missing compared with gnumeric:
623 // "m/d/yy h:mm", /* 20 */
624 // "m/d/yyyy h:mm", /* 21 */
625 // "mmm/ddd/yy", /* 12 */
626 // "mmm/ddd/yyyy", /* 13 */
627 // "mm/ddd/yy", /* 14 */
628 // "mm/ddd/yyyy", /* 15 */
629
630 return tmp;
631}
632
633TQString ValueFormatter::errorFormat (Cell *cell)
634{
635 TQString err;
636 if (cell->testFlag (Cell::Flag_ParseError))
637 err = "#" + i18n ("Parse") + "!";
638 else if ( cell->testFlag (Cell::Flag_CircularCalculation))
639 err = "#" + i18n ("Circle") + "!";
640 else if ( cell->testFlag (Cell::Flag_DependancyError))
641 err = "#" + i18n ("Depend") + "!";
642 else
643 {
644 err = "####";
645 kdDebug(36001) << "Unhandled error type." << endl;
646 }
647 return err;
648}
649
For every cell in the spread sheet there is a Cell object.
The ValueConverter class can convert Value between various different formats.
TQString createNumberFormat(double value, int precision, FormatType fmt, bool alwaysSigned, const TQString &currencySymbol)
create a number format
void removeTrailingZeros(TQString &str, TQChar decimal_point)
Remove trailing zeros and the decimal point if necessary unless the number has no decimal point.
ValueFormatter(ValueConverter *converter)
copnstructor
TQString timeFormat(const TQDateTime &_time, FormatType fmtType)
create a time format
TQString fractionFormat(double value, FormatType fmtType)
create a fraction format
TQString dateFormat(const TQDate &_date, FormatType fmtType)
create a date format
TQString formatText(Cell *cell, FormatType fmtType)
create a text representation of data in this cell
FormatType determineFormatting(const Value &value, FormatType fmtType)
determine the formatting type that should be used to format this value in a cell with a given format ...
Provides a wrapper for cell value.
Value element(unsigned column, unsigned row) const
Return an element in the array value.
Format format() const
Returns the format of the value (i.e.
bool isBoolean() const
Returns true if the type of this value is Boolean.
bool isArray() const
Returns true if the type of this value is array.
TQDate asDate() const
Returns the date representation of this value.
double asFloat() const
Returns the floating-point value of this value.
TQDateTime asDateTime() const
Returns the date/time representation of this value.
bool isString() const
Returns true if the type of this value is string.
The KSpread namespace.
Definition commands.h:39