format.c 45.8 KB
Newer Older
1
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
/* format.c - attempts to emulate excel's number formatting ability.
Arturo Espinosa's avatar
Arturo Espinosa committed
3
 * Copyright (C) 1998 Chris Lahey, Miguel de Icaza
4
 *
5 6
 * Redid the format parsing routine to make it accept more of the Excel
 * formats.  The number rendeing code from Chris has not been touched,
Arturo Espinosa's avatar
Arturo Espinosa committed
7
 * that routine is pretty good.
8
 *
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

24
#include <gnumeric-config.h>
Zbigniew Chyla's avatar
New  
Zbigniew Chyla committed
25
#include <gnumeric-i18n.h>
26
#include "gnumeric.h"
27
#include "numbers.h"
28
#include "format.h"
29 30

#include "style-color.h"
Jody Goldberg's avatar
Jody Goldberg committed
31
#include "expr.h"
32
#include "dates.h"
33
#include "value.h"
34
#include "parse-util.h"
Morten Welinder's avatar
Morten Welinder committed
35
#include "datetime.h"
36
#include "mathfunc.h"
Jody Goldberg's avatar
Jody Goldberg committed
37
#include "str.h"
38
#include "number-match.h"
39
#include "formats.h"
40 41 42

#include <locale.h>
#include <ctype.h>
Jody Goldberg's avatar
Jody Goldberg committed
43
#include <string.h>
44 45 46 47 48 49
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#ifdef HAVE_LANGINFO_H
#    include <langinfo.h>
#endif
50

Jody Goldberg's avatar
Jody Goldberg committed
51 52
/***************************************************************************/

53
/* Points to the locale information for number display */
Miguel de Icaza's avatar
Miguel de Icaza committed
54
static struct lconv *lc = NULL;
55
static char *locale_currency = NULL; /* in UTF-8 */
Jody Goldberg's avatar
Jody Goldberg committed
56
static gboolean date_order_cached = FALSE;
57

Jody Goldberg's avatar
Jody Goldberg committed
58 59 60 61
char const *
gnumeric_setlocale (int category, char const *val)
{
	lc = NULL;
62
	g_free (locale_currency); locale_currency = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
63
	date_order_cached = FALSE;
Jody Goldberg's avatar
Jody Goldberg committed
64 65 66 67 68 69 70 71 72 73
	return setlocale (category, val);
}

char
format_get_decimal (void)
{
	char res;
	if (lc == NULL)
		lc = localeconv ();

Morten Welinder's avatar
Morten Welinder committed
74
	/* NOTE : Use decimal_point _not_ mon_decimal_point.  strtognum uses this
75 76 77
	 * and we get very confused when they are different (eg ru_RU)
	 */
	res = lc->decimal_point[0];
Jody Goldberg's avatar
Jody Goldberg committed
78 79 80 81 82 83 84 85 86 87
	return (res != '\0') ? res : '.';
}

char
format_get_thousand (void)
{
	char res;
	if (lc == NULL)
		lc = localeconv ();

Morten Welinder's avatar
Morten Welinder committed
88
	res = lc->mon_thousands_sep[0];
Jody Goldberg's avatar
Jody Goldberg committed
89 90
	if (res != '\0')
		return res;
Morten Welinder's avatar
Morten Welinder committed
91

Jody Goldberg's avatar
Jody Goldberg committed
92 93 94 95 96 97
	/* Provide a decent default for countries using ',' as a decimal */
	if (format_get_decimal () != ',')
		return ',';
	return '.';
}

98 99 100 101 102 103
/**
 * format_get_currency :
 * @precedes : a pointer to a boolean which is set to TRUE if the currency
 * 		should precede
 * @space_sep: a pointer to a boolean which is set to TRUE if the currency
 * 		should have a space separating it from the the value
104 105 106
 *
 * Play with the default logic so that things come out nicely for the default
 * case.
107
 */
108
char const *
109
format_get_currency (gboolean *precedes, gboolean *space_sep)
110 111 112 113
{
	if (lc == NULL)
		lc = localeconv ();

114
	/* Use != 0 rather than == 1 so that CHAR_MAX (undefined) is true */
115 116
	if (precedes)
		*precedes = (lc->p_cs_precedes != 0);
117
	/* Use == 1 rather than != 0 so that CHAR_MAX (undefined) is false */
118
	if (space_sep)
119
		*space_sep = (lc->p_sep_by_space == 1);
120

121 122 123 124 125 126 127 128
	if (locale_currency == NULL) {
		locale_currency = (lc->currency_symbol == NULL ||
				   *lc->currency_symbol == '\0')
			? g_strdup ("$")
			: g_locale_to_utf8 (lc->currency_symbol, -1,
					    NULL, NULL, NULL);
	}
	return locale_currency;
129 130
}

Jody Goldberg's avatar
Jody Goldberg committed
131 132 133 134 135 136
/*
 * format_month_before_day :
 *
 * A quick utility routine to guess whether the default date format
 * uses day/month or month/day
 */
137 138 139
gboolean
format_month_before_day (void)
{
140
#ifdef HAVE_LANGINFO_H
Jody Goldberg's avatar
Jody Goldberg committed
141 142 143
	static gboolean month_first = TRUE;

	if (!date_order_cached) {
Morten Welinder's avatar
Morten Welinder committed
144
		char const *ptr = nl_langinfo (D_FMT);
Jody Goldberg's avatar
Jody Goldberg committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

		date_order_cached = TRUE;
		month_first = TRUE;
		if (ptr)
			while (*ptr) {
				char const tmp = tolower((unsigned char)*ptr++);
				if (tmp == 'd') {
					month_first = FALSE;
					break;
				} else if (tmp == 'm')
					break;
			}
	}

	return month_first;
160 161 162 163 164 165 166 167
#else
	static gboolean warning = TRUE;
	if (warning) {
		g_warning ("Incomplete locale library, dates will be month day year");
		warning = FALSE;
	}
	return TRUE;
#endif
168 169
}

Morten Welinder's avatar
Morten Welinder committed
170
/* Use comma as the arg separator unless the decimal point is a
Jody Goldberg's avatar
Jody Goldberg committed
171 172 173 174 175 176 177 178 179 180 181
 * comma, in which case use a semi-colon
 */
char
format_get_arg_sep (void)
{
	if (format_get_decimal () == ',')
		return ';';
	return ',';
}

char
182
format_get_col_sep (void)
Jody Goldberg's avatar
Jody Goldberg committed
183 184 185 186 187 188 189 190 191
{
	if (format_get_decimal () == ',')
		return '\\';
	return ',';
}
/***************************************************************************/

/* WARNING : Global */
static GHashTable *style_format_hash = NULL;
192

193
typedef struct {
194 195
        char const *format;
	gboolean    want_am_pm;
196
	gboolean    has_fraction;
197
        char        restriction_type;
198
        gnum_float  restriction_value;
Jody Goldberg's avatar
Jody Goldberg committed
199
	StyleColor *color;
200 201
} StyleFormatEntry;

Chris Lahey's avatar
Chris Lahey committed
202
/*
203 204 205 206 207 208 209 210 211 212 213
 * The returned string is newly allocated.
 *
 * Current format is an optional date specification followed by an
 * optional number specification.
 *
 * A date specification is an arbitrary sequence of characters (other
 * than '#', '0', '?', or '.') which is copied to the output.  The
 * standard date fields are substituted for.  If it ever finds an a or
 * a p it lists dates in 12 hour time, otherwise, it lists dates in 24
 * hour time.
 *
214
 * A number specification is as described in the relevant portions of
215
 * the excel formatting information.  Commas can currently only appear
216 217
 * at the end of the number specification.  Fractions are supported
 * but the parsing is not as nice as it should be.
218
 */
219

220

221 222 223 224
/*
 * Parses the year field at the beginning of the format.  Returns the
 * number of characters used.
 */
225
static int
226
append_year (GString *string, const guchar *format, const struct tm *time_split)
227
{
Morten Welinder's avatar
Morten Welinder committed
228
	char temp[5];
Arturo Espinosa's avatar
Arturo Espinosa committed
229

Morten Welinder's avatar
Morten Welinder committed
230
	if (tolower (format[1]) != 'y'){
231 232 233
		g_string_append_c (string, 'y');
		return 1;
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
234

Morten Welinder's avatar
Morten Welinder committed
235
	if (tolower (format[2]) != 'y' || tolower (format[3]) != 'y'){
Miguel de Icaza's avatar
Miguel de Icaza committed
236
		sprintf (temp, "%02d", time_split->tm_year % 100);
237 238 239 240 241 242 243 244
		g_string_append (string, temp);
		return 2;
	}

	sprintf (temp, "%04d", time_split->tm_year + 1900);
	g_string_append (string, temp);

	return 4;
245 246
}

247 248 249 250 251
/*
 * Parses the month field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
252
append_month (GString *string, int n, const struct tm *time_split)
253
{
Morten Welinder's avatar
Morten Welinder committed
254
	char temp[3];
Arturo Espinosa's avatar
Arturo Espinosa committed
255

256
	if (n == 1){
Morten Welinder's avatar
Morten Welinder committed
257
		sprintf (temp, "%d", time_split->tm_mon + 1);
258
		g_string_append (string, temp);
259 260
		return 1;
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
261

262
	if (n == 2){
Morten Welinder's avatar
Morten Welinder committed
263
		sprintf (temp, "%02d", time_split->tm_mon + 1);
264 265 266
		g_string_append (string, temp);
		return 2;
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
267

268
	if (n == 3){
Morten Welinder's avatar
Morten Welinder committed
269
		g_string_append (string, _(month_short[time_split->tm_mon]) + 1);
270 271 272
		return 3;
	}

Morten Welinder's avatar
Morten Welinder committed
273
	g_string_append (string, _(month_long[time_split->tm_mon]));
274
	return 4;
275 276
}

277 278 279 280 281
/*
 * Parses the day field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
282
append_day (GString *string, const guchar *format, const struct tm *time_split)
283
{
284 285
	char temp[3];

Morten Welinder's avatar
Morten Welinder committed
286
	if (tolower (format[1]) != 'd'){
287 288 289 290 291
		sprintf (temp, "%d", time_split->tm_mday);
		g_string_append (string, temp);
		return 1;
	}

Morten Welinder's avatar
Morten Welinder committed
292
	if (tolower (format[2]) != 'd'){
293 294 295 296 297
		sprintf (temp, "%02d", time_split->tm_mday);
		g_string_append (string, temp);
		return 2;
	}

Morten Welinder's avatar
Morten Welinder committed
298 299
	if (tolower (format[3]) != 'd'){
		g_string_append (string, _(day_short[time_split->tm_wday]) + 1);
300 301 302 303 304 305
		return 3;
	}

	g_string_append (string, _(day_long[time_split->tm_wday]));

	return 4;
306 307
}

308
/*
309
 * Renders the hour.
310
 */
311
static void
Jody Goldberg's avatar
Jody Goldberg committed
312
append_hour (GString *string, int n, struct tm const *time_split,
313
	     gboolean want_am_pm)
314
{
315
	char *temp = g_alloca (n + 4 * sizeof (int));
316

317 318
	sprintf (temp, "%0*d", n,
		 want_am_pm
319
		 ? (((time_split->tm_hour + 11) % 12) + 1)
320
		 : time_split->tm_hour);
321 322 323 324 325 326 327
	g_string_append (string, temp);
}

/*
 * Renders the hour.
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
328
append_hour_elapsed (GString *string, struct tm const *time_split, int number)
329
{
Jody Goldberg's avatar
Jody Goldberg committed
330
	char buf[(DBL_MANT_DIG + DBL_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
331 332 333
	double hours = number * 24. + time_split->tm_hour;
	snprintf (buf, sizeof (buf), "%.0f", hours);
	g_string_append (string, buf);
334 335
}

336
/*
337
 * Renders the number of minutes.
338
 */
339
static void
Jody Goldberg's avatar
Jody Goldberg committed
340
append_minute (GString *string, int n, struct tm const *time_split)
341
{
342
	char *temp = g_alloca (n + 4 * sizeof (int));
343 344 345 346 347 348 349 350
	sprintf (temp, "%0*d", n, time_split->tm_min);
	g_string_append (string, temp);
}

/*
 * Renders the number of minutes, in elapsed format
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
351
append_minute_elapsed (GString *string, struct tm const *time_split, int number)
352
{
Jody Goldberg's avatar
Jody Goldberg committed
353
	char buf[(DBL_MANT_DIG + DBL_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
354 355 356
	double minutes = ((number * 24.) + time_split->tm_hour) * 60. + time_split->tm_min;
	snprintf (buf, sizeof (buf), "%.0f", minutes);
	g_string_append (string, buf);
357 358 359 360 361 362
}

/*
 * Renders the second field.
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
363
append_second (GString *string, int n, struct tm const *time_split)
364
{
365
	char *temp = g_alloca (n + 4 * sizeof (int));
366 367 368 369 370
	sprintf (temp, "%0*d", n, time_split->tm_sec);
	g_string_append (string, temp);
}

/*
Jody Goldberg's avatar
Jody Goldberg committed
371
 * Renders the second field in elapsed
372 373
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
374
append_second_elapsed (GString *string, int n, struct tm const *time_split, int number)
375
{
Jody Goldberg's avatar
Jody Goldberg committed
376
	char buf[(DBL_MANT_DIG + DBL_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
377 378 379
	double seconds = (((number * 24. + time_split->tm_hour) * 60. + time_split->tm_min) * 60.) + time_split->tm_sec;
	snprintf (buf, sizeof (buf), "%.0f", seconds);
	g_string_append (string, buf);
380 381
}

Jody Goldberg's avatar
Jody Goldberg committed
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
static StyleFormatEntry *
format_entry_ctor (void)
{
	StyleFormatEntry *entry;

	entry = g_new (StyleFormatEntry, 1);
	entry->restriction_type = '*';
	entry->restriction_value = 0.;
	entry->want_am_pm = entry->has_fraction = FALSE;
	entry->color = NULL;
	return entry;
}

/**
 * format_entry_dtor :
 *
 * WARNING : do not call this for temporary formats generated for
 * 'General'.
400 401
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
402
format_entry_dtor (gpointer data, gpointer user_data)
403
{
Jody Goldberg's avatar
Jody Goldberg committed
404 405 406 407
	StyleFormatEntry *entry = data;
	if (entry->color != NULL) {
		style_color_unref (entry->color);
		entry->color = NULL;
408
	}
Jody Goldberg's avatar
Jody Goldberg committed
409 410
	g_free ((char *)entry->format);
	g_free (entry);
411 412
}

413 414
static void
format_entry_set_fmt (StyleFormatEntry *entry,
Jody Goldberg's avatar
Jody Goldberg committed
415 416
		      gchar const *begin,
		      gchar const *end)
417 418 419 420 421 422 423 424 425
{
	/* empty formats are General if there is a color, or a condition */
	entry->format = (begin != NULL && end != begin)
		? g_strndup (begin, end - begin)
		: strdup ((entry->color != NULL ||
			   entry->restriction_type != '*')
			  ? "General" : "");
}

Jody Goldberg's avatar
Jody Goldberg committed
426
static StyleColor * lookup_color (const char *str, const char *end);
Chris Lahey's avatar
Chris Lahey committed
427

428
/*
Jody Goldberg's avatar
Jody Goldberg committed
429 430 431 432
 * Since the Excel formating codes contain a number of ambiguities, this
 * routine does some analysis on the format first.  This routine should always
 * return, it cannot fail, in the worst case it should just downgrade to
 * simplistic formatting
Chris Lahey's avatar
Chris Lahey committed
433
 */
Jody Goldberg's avatar
Jody Goldberg committed
434
static void
435 436
format_compile (StyleFormat *format)
{
Jody Goldberg's avatar
Jody Goldberg committed
437
	gchar const *fmt, *real_start = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
438
	StyleFormatEntry *entry = format_entry_ctor ();
439 440
	int num_entries = 1, counter = 0;
	GSList *ptr;
Jody Goldberg's avatar
Jody Goldberg committed
441

442
	format_match_create (format);
Jody Goldberg's avatar
Jody Goldberg committed
443 444 445 446 447 448
	for (fmt = format->format; *fmt ; fmt++) {
		if (NULL == real_start && '[' != *fmt)
			real_start = fmt;

		switch (*fmt) {
		case '[': {
Jody Goldberg's avatar
Jody Goldberg committed
449 450
			gchar const *begin = fmt + 1;
			gchar const *end = begin;
Jody Goldberg's avatar
Jody Goldberg committed
451

452 453 454 455
			/* find end checking for escapes but not quotes ?? */
			for (; end[0] != ']' && end[1] != '\0' ; ++end)
				if (*end == '\\')
					end++;
Jody Goldberg's avatar
Jody Goldberg committed
456 457 458

			/* Check for conditional */
			if (*begin == '<') {
Morten Welinder's avatar
Morten Welinder committed
459
				if (begin[1] == '=') {
Jody Goldberg's avatar
Jody Goldberg committed
460 461
					entry->restriction_type = ',';
					begin += 2;
Morten Welinder's avatar
Morten Welinder committed
462
				} else if (begin[1] == '>') {
Jody Goldberg's avatar
Jody Goldberg committed
463 464 465 466 467 468 469
					entry->restriction_type = '+';
					begin += 2;
				} else {
					entry->restriction_type = '<';
					begin++;
				}
			} else if (*begin == '>') {
Morten Welinder's avatar
Morten Welinder committed
470
				if (begin[1] == '=') {
Jody Goldberg's avatar
Jody Goldberg committed
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
					entry->restriction_type = '.';
					begin += 2;
				} else {
					entry->restriction_type = '>';
					begin++;
				}
			} else if (*begin == '=') {
				entry->restriction_type = '=';
			} else if (*begin != '$' && entry->color == NULL) {
				entry->color = lookup_color (begin, end);
				/* Only the first colour counts */
				if (NULL == entry->color) {
					if (NULL == real_start)
						real_start = fmt;
					continue;
				}
				fmt = end;
				continue;
			} else {
				if (NULL == real_start)
					real_start = fmt;
				continue;
493
			}
Jody Goldberg's avatar
Jody Goldberg committed
494
			fmt = end;
495 496 497

			/* fall back on 0 for errors */
			errno = 0;
Morten Welinder's avatar
Morten Welinder committed
498
			entry->restriction_value = strtognum (begin, (char **)&end);
499 500
			if (errno == ERANGE || begin == end)
				entry->restriction_value = 0.;
501
			break;
Jody Goldberg's avatar
Jody Goldberg committed
502 503 504 505 506 507 508 509 510 511 512
		}

		case '\\' :
			if (fmt[1] != '\0')
				fmt++; /* skip escaped characters */
			break;

		case '\'' :
		case '\"' : {
			/* skip quoted strings */
			char const match = *fmt;
513 514
			for (; fmt[0] != match && fmt[1] != '\0'; fmt++)
				if (*fmt == '\\')
Jody Goldberg's avatar
Jody Goldberg committed
515
					fmt++;
516 517
			break;
		}
Chris Lahey's avatar
Chris Lahey committed
518

Jody Goldberg's avatar
Jody Goldberg committed
519
		case '/':
Morten Welinder's avatar
Morten Welinder committed
520
			if (fmt[1] == '?' || isdigit ((unsigned char)fmt[1])) {
Jody Goldberg's avatar
Jody Goldberg committed
521 522 523 524
				entry->has_fraction = TRUE;
				fmt++;
			}
			break;
Arturo Espinosa's avatar
Arturo Espinosa committed
525

Jody Goldberg's avatar
Jody Goldberg committed
526 527
		case 'a': case 'A':
		case 'p': case 'P':
Morten Welinder's avatar
Morten Welinder committed
528
			if (tolower (fmt[1]) == 'm')
Jody Goldberg's avatar
Jody Goldberg committed
529 530
				entry->want_am_pm = TRUE;
			break;
Miguel de Icaza's avatar
Miguel de Icaza committed
531

532 533
		case ';':
			format_entry_set_fmt (entry, real_start, fmt);
534
			format->entries = g_slist_append (format->entries, entry);
535 536 537 538 539 540
			num_entries++;

			entry = format_entry_ctor ();
			real_start = NULL;
			break;

Jody Goldberg's avatar
Jody Goldberg committed
541 542 543
		default :
			break;
		}
544
	}
Miguel de Icaza's avatar
Miguel de Icaza committed
545

546
	format_entry_set_fmt (entry, real_start, fmt);
547 548 549 550 551
	format->entries = g_slist_append (format->entries, entry);

	for (ptr = format->entries; ptr && counter++ < 4 ; ptr = ptr->next) {
		StyleFormatEntry *entry = ptr->data;

552 553 554 555 556
		/*
		 * Use default conditional instead of catch-all, except
		 * for the last entry.
		 */
		if (entry->restriction_type == '*' && counter != num_entries) {
557 558 559
			entry->restriction_value = 0.;

			switch (counter) {
560
			case 1 : entry->restriction_type = (num_entries > 2) ? '>' : '.';
561
				 break;
562 563 564
			case 2 : entry->restriction_type = '<'; break;
			case 3 : entry->restriction_type = '='; break;
			case 4 : entry->restriction_type = '@'; break;
565 566 567 568 569
			default :
				 break;
			}
		}
	}
Jody Goldberg's avatar
Jody Goldberg committed
570 571
}

Miguel de Icaza's avatar
Miguel de Icaza committed
572 573 574 575 576 577 578
/*
 * This routine is invoked when the last user of the
 * format is gone (ie, refcount has reached zero) just
 * before the StyleFormat structure is actually released.
 *
 * resources allocated in format_compile should be disposed here
 */
579 580 581
void
format_destroy (StyleFormat *format)
{
582 583
	g_slist_foreach (format->entries, &format_entry_dtor, NULL);
	g_slist_free (format->entries);
Jody Goldberg's avatar
Jody Goldberg committed
584
	format->entries = NULL;
585
	format_match_release (format);
586 587
}

588
static struct FormatColor {
Jon Kåre Hellan's avatar
Jon Kåre Hellan committed
589
	const char *name;
590
	StyleColor *color;
Morten Welinder's avatar
Morten Welinder committed
591
} format_colors[] = {
592 593 594 595 596 597 598 599 600
	{ N_("black")   },
	{ N_("blue")    },
	{ N_("cyan")    },
	{ N_("green")   },
	{ N_("magenta") },
	{ N_("red")     },
	{ N_("white")   },
	{ N_("yellow")  },
	{ NULL, NULL }
601 602
};

603 604 605 606 607
void
format_color_init (void)
{
	int i;

608 609 610
	for (i = 0; format_colors[i].name; i++)
		format_colors[i].color =
			style_color_new_name (format_colors[i].name);
611 612 613 614 615 616 617
}

void
format_color_shutdown (void)
{
	int i;

Morten Welinder's avatar
Morten Welinder committed
618 619
	for (i = 0; format_colors[i].name; i++)
		style_color_unref (format_colors[i].color);
620 621
}

622
static struct FormatColor const *
Jody Goldberg's avatar
Jody Goldberg committed
623
lookup_color_by_name (gchar const *str, gchar const *end,
624
		      gboolean const translate)
625
{
626
	int i, len;
627

628
	len = end - str;
Morten Welinder's avatar
Morten Welinder committed
629
	for (i = 0; format_colors[i].name; i++) {
Jody Goldberg's avatar
Jody Goldberg committed
630
		gchar const *name = format_colors[i].name;
631 632 633
		if (translate)
			name = _(name);

634
		if (0 == g_ascii_strncasecmp (name, str, len) && name[len] == '\0')
635 636 637 638 639 640
			return format_colors + i;
	}
	return NULL;
}

static StyleColor *
Jody Goldberg's avatar
Jody Goldberg committed
641
lookup_color (const gchar *str, const gchar *end)
642 643 644 645 646 647
{
	struct FormatColor const *color = lookup_color_by_name (str, end, FALSE);

	if (color != NULL) {
		style_color_ref (color->color);
		return color->color;
648 649 650 651
	}
	return NULL;
}

652
static gnum_float beyond_precision;
653
void
654
render_number (GString *result,
655
	       gnum_float number,
656
	       format_info_t const *info)
657
{
658
	char thousands_sep = format_get_thousand ();
Morten Welinder's avatar
Morten Welinder committed
659
	char num_buf[(GNUM_MANT_DIG + GNUM_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
660
	gchar *num = num_buf + sizeof (num_buf) - 1;
661
	gnum_float frac_part, int_part;
662
	int group, zero_count, digit_count = 0;
663 664 665 666 667
	int left_req = info->left_req;
	int right_req = info->right_req;
	int left_spaces = info->left_spaces;
	int right_spaces = info->right_spaces;
	int right_allowed = info->right_allowed + info->right_optional;
668

669 670 671
	if (right_allowed >= 0 && !info->has_fraction) {
		/* Change "rounding" into "truncating".   */
		/* Note, that we assume number >= 0 here. */
672
		gnum_float delta = 5 * gpow10 (-right_allowed - 1);
673 674
		number += delta;
	}
Morten Welinder's avatar
Morten Welinder committed
675
	frac_part = modfgnum (gnumeric_add_epsilon (number), &int_part);
676

677
	*num = '\0';
678
	group = (info->group_thousands) ? 3 : -1;
679 680 681
	for (; int_part > beyond_precision ; int_part /= 10., digit_count++) {
		if (group-- == 0) {
			group = 2;
682
			*(--num) = thousands_sep;
683
		}
684
		*(--num) = '0';
685
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
686

687
	for (; int_part >= 1. ; int_part /= 10., digit_count++) {
688 689
		gnum_float r = floorgnum (int_part);
		int digit = r - floorgnum (r / 10) * 10;
690

691 692
		if (group-- == 0) {
			group = 2;
693 694 695 696
			*(--num) = thousands_sep;
		}
		*(--num) = digit + '0';
	}
697

698
	/* TODO : What ifthe only visible digits are zeros ? -0.00 looks bad */
699
	if (info->negative && !info->supress_minus)
700 701
		g_string_append_c (result, '-');
	if (left_req > digit_count) {
702
		for (left_spaces -= left_req ; left_spaces-- > 0 ;)
703 704 705 706 707 708
			g_string_append_c (result, ' ');
		for (left_req -= digit_count ; left_req-- > 0 ;)
			g_string_append_c (result, '0');
	}

	g_string_append (result, num);
709

710 711 712 713 714 715 716 717 718
	/* If the format contains only "#"s to the left of the decimal
	 * point, number in the [0.0,1.0] range are prefixed with a
	 * decimal point
	 */
	if (info->decimal_separator_seen ||
	    (number > 0.0 &&
	     number < 1.0 &&
	     info->right_allowed == 0 &&
	     info->right_optional > 0))
719
		g_string_append_c (result, format_get_decimal ());
720

721 722 723 724 725
	/* TODO : clip this a DBL_DIG */
	/* TODO : What if is a fraction ? */
	right_allowed -= right_req;
	right_spaces  -= right_req;
	while (right_req-- > 0) {
Arturo Espinosa's avatar
Arturo Espinosa committed
726
		gint digit;
727 728 729
		frac_part *= 10.0;
		digit = (gint)frac_part;
		frac_part -= digit;
730
		g_string_append_c (result, digit + '0');
Arturo Espinosa's avatar
Arturo Espinosa committed
731 732 733 734
	}

	zero_count = 0;

735
	while (right_allowed-- > 0) {
Arturo Espinosa's avatar
Arturo Espinosa committed
736
		gint digit;
737 738 739
		frac_part *= 10.0;
		digit = (gint)frac_part;
		frac_part -= digit;
Arturo Espinosa's avatar
Arturo Espinosa committed
740

741
		if (digit == 0) {
Arturo Espinosa's avatar
Arturo Espinosa committed
742 743
			right_spaces -= zero_count + 1;
			zero_count = 0;
744 745
		} else
			zero_count ++;
Arturo Espinosa's avatar
Arturo Espinosa committed
746

747
		g_string_append_c (result, digit + '0');
Arturo Espinosa's avatar
Arturo Espinosa committed
748 749
	}

750
	g_string_truncate (result, result->len - zero_count);
Arturo Espinosa's avatar
Arturo Espinosa committed
751

752
	while (right_spaces-- > 0)
753
		g_string_append_c (result, ' ');
754 755
}

756
static void
757
do_render_number (gnum_float number, format_info_t *info, GString *result)
758
{
759
	info->rendered = TRUE;
Chris Lahey's avatar
Chris Lahey committed
760

Arturo Espinosa's avatar
Arturo Espinosa committed
761
#if 0
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
	printf ("Rendering: %g with:\n", number);
	printf ("left_req:    %d\n"
		"right_req:   %d\n"
		"left_spaces: %d\n"
		"right_spaces:%d\n"
		"right_allow: %d\n"
		"negative:    %d\n"
		"supress:     %d\n"
		"decimalseen: %d\n"
		"decimalp:    %s\n",
		info->left_req,
		info->right_req,
		info->left_spaces,
		info->right_spaces,
		info->right_allowed + info->right_optional,
		info->negative,
		info->supress_minus,
		info->decimal_separator_seen,
		decimal_point);
#endif
Arturo Espinosa's avatar
Arturo Espinosa committed
782

783
	render_number (result, info->scale * number, info);
784 785
}

786 787 788 789 790 791 792
/*
 * Microsoft Excel has a bug in the handling of year 1900,
 * I quote from http://catless.ncl.ac.uk/Risks/19.64.html#subj9.1
 *
 * > Microsoft EXCEL version 6.0 ("Office 95 version") and version 7.0 ("Office
 * > 97 version") believe that year 1900 is a leap year.  The extra February 29
 * > cause the following problems.
Arturo Espinosa's avatar
Arturo Espinosa committed
793
 * >
794 795 796
 * > 1)  All day-of-week before March 1, 1900 are incorrect;
 * > 2)  All date sequence (serial number) on and after March 1, 1900 are incorrect.
 * > 3)  Calculations of number of days across March 1, 1900 are incorrect.
Arturo Espinosa's avatar
Arturo Espinosa committed
797
 * >
798 799 800 801
 * > The risk of the error will cause must be little.  Especially case 1.
 * > However, import or export date using serial date number will be a problem.
 * > If no one noticed anything wrong, it must be that no one did it that way.
 */
802
static struct tm *
803
split_time (gnum_float number)
804 805
{
	static struct tm tm;
806
	guint secs;
807
	GDate* date;
808

Morten Welinder's avatar
Morten Welinder committed
809
	date = datetime_serial_to_g (datetime_serial_raw_to_serial (number));
810
	g_date_to_struct_tm (date, &tm);
Morten Welinder's avatar
Morten Welinder committed
811
	g_date_free (date);
812

Morten Welinder's avatar
Morten Welinder committed
813
	secs = datetime_serial_raw_to_seconds (number);
814 815 816 817 818
	tm.tm_hour = secs / 3600;
	secs -= tm.tm_hour * 3600;
	tm.tm_min  = secs / 60;
	secs -= tm.tm_min * 60;
	tm.tm_sec  = secs;
819 820 821 822

	return &tm;
}

823 824 825 826
/*
 * Finds the decimal char in @str doing the proper parsing of a
 * format string
 */
827 828
static char const *
find_decimal_char (char const *str)
829 830
{
	for (;*str; str++){
Jody Goldberg's avatar
Jody Goldberg committed
831
		if (*str == '.')
832 833
			return str;

Jody Goldberg's avatar
Jody Goldberg committed
834
		if (*str == ',')
835 836 837 838 839
			continue;

		switch (*str){
			/* These ones do not have any argument */
		case '#': case '?': case '0': case '%':
Jody Goldberg's avatar
Jody Goldberg committed
840
		case '-': case '+': case ')': case '£':
Jody Goldberg's avatar
Jody Goldberg committed
841
		case ':': case '$': case '¥': case '¤':
842 843 844 845 846 847 848
		case 'M': case 'm': case 'D': case 'd':
		case 'Y': case 'y': case 'S': case 's':
		case '*': case 'h': case 'H': case 'A':
		case 'a': case 'P': case 'p':
			break;

			/* Quoted string */
Jody Goldberg's avatar
Jody Goldberg committed
849
		case '"':
850 851 852 853 854 855
			for (str++; *str && *str != '"'; str++)
				;
			break;

			/* Escaped char and spacing format */
		case '\\': case '_':
Morten Welinder's avatar
Morten Welinder committed
856
			if (*(str + 1))
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
				str++;
			break;

			/* Scientific number */
		case 'E': case 'e':
			for (str++; *str;){
				if (*str == '+')
					str++;
				else if (*str == '-')
					str++;
				else if (*str == '0')
					str++;
				else
					break;
			}
		}
	}
	return NULL;
}

/*
 * This routine scans the format_string for a decimal dot,
 * and if it finds it, it removes the first zero after it to
 * reduce the display precision for the number.
 *
 * Returns NULL if the new format would not change things
 */
char *
Jody Goldberg's avatar
Jody Goldberg committed
885
format_remove_decimal (StyleFormat const *fmt)
886
{
887
	int offset = 1;
888
	char *ret, *p;
889
	char const *tmp;
Jody Goldberg's avatar
Jody Goldberg committed
890
	char const *format_string = fmt->format;
891 892

	/*
893 894 895 896
	 * Consider General format as 0. with several optional decimal places.
	 * This is WRONG.  FIXME FIXME FIXME
	 * We need to look at the number of decimals in the current value
	 * and use that as a base.
897 898
	 */
	if (strcmp (format_string, "General") == 0)
899
		format_string = "0.########";
Jody Goldberg's avatar
Jody Goldberg committed
900

901 902
	tmp = find_decimal_char (format_string);
	if (!tmp)
903
		return NULL;
904

905 906
	ret = g_strdup (format_string);
	p = ret + (tmp - format_string);
907

908 909 910 911 912 913
	/* If there is more than 1 thing after the decimal place
	 * leave the decimal.
	 * If there is only 1 thing after the decimal remove the decimal too.
	 */
	if ((p[1] == '0' || p[1] == '#') && (p[2] == '0' || p[2] == '#'))
		++p;
914 915
	else
		offset = 2;
916

917
	strcpy (p, p + offset);
918 919 920 921 922 923 924 925 926 927 928 929 930

	return ret;
}

/*
 * This routine scans format_string for the decimal
 * character and when it finds it, it adds a zero after
 * it to force the rendering of the number with one more digit
 * of decimal precision.
 *
 * Returns NULL if the new format would not change things
 */
char *
Jody Goldberg's avatar
Jody Goldberg committed
931
format_add_decimal (StyleFormat const *fmt)
932
{
933 934 935
	char const *pre = NULL;
	char const *post = NULL;
	char *res;
Jody Goldberg's avatar
Jody Goldberg committed
936
	char const *format_string = fmt->format;
937

938
	if (strcmp (format_string, "General") == 0) {
939
		format_string = "0";
Morten Welinder's avatar
Morten Welinder committed
940
		pre = format_string + 1;
941
		post = pre;
942
	} else {
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962
		pre = find_decimal_char (format_string);

		/* If there is no decimal append to the last '0' */
		if (pre == NULL) {
			pre = strrchr (format_string, '0');

			/* If there are no 0s append to the ':s' */
			if (pre == NULL) {
				pre = strrchr (format_string, 's');
				if (pre > format_string && pre[-1] == ':') {
					if (pre[1] == 's')
						pre += 2;
					else
						++pre;
				} else
					return NULL;
			} else
				++pre;
			post = pre;
		} else
Morten Welinder's avatar
Morten Welinder committed
963
			post = pre + 1;
964
	}
965 966 967 968 969 970
	res = g_malloc ((pre - format_string + 1) +
		      1 + /* for the decimal */
		      1 + /* for the extra 0 */
		      strlen (post) +
		      1 /*terminate */);
	if (!res)
971 972
		return NULL;

973
	strncpy (res, format_string, pre - format_string);
Morten Welinder's avatar
Morten Welinder committed
974 975
	res[pre-format_string + 0] = '.';
	res[pre-format_string + 1] = '0';
976 977 978
	strcpy (res + (pre - format_string) + 2, post);

	return res;
979 980
}

Jody Goldberg's avatar
Jody Goldberg committed
981 982
/*********************************************************************/

983
static gchar *
984
format_number (gnum_float number, int col_width, StyleFormatEntry const *entry)
985 986
{
	GString *result = g_string_new ("");
Jody Goldberg's avatar
Jody Goldberg committed
987
	guchar const *format = (guchar *)(entry->format);
988
	format_info_t info;
Jody Goldberg's avatar
Jody Goldberg committed
989
	gboolean can_render_number = FALSE;
990
	int hour_seen = 0;
991 992
	gboolean time_display_elapsed = FALSE;
	gboolean ignore_further_elapsed = FALSE;
993 994 995 996

	char fill_char = '\0';
	int fill_start = -1;

997
	struct tm *time_split = 0;
998
	char *res;
999
	gnum_float signed_number;
Arturo Espinosa's avatar
Arturo Espinosa committed
1000

1001
	memset (&info, 0, sizeof (info));
1002
	signed_number = number;
1003 1004 1005
	if (number < 0.0){
		info.negative = TRUE;
		number = -number;
Chris Lahey's avatar
Chris Lahey committed
1006
	}
1007
	info.has_fraction = entry->has_fraction;
1008
	info.scale = 1;
1009

Jody Goldberg's avatar
Jody Goldberg committed
1010
	while (*format) {
Morten Welinder's avatar
Morten Welinder committed
1011 1012 1013
		int c = *format;

		switch (c) {
1014 1015

		case '[':
1016 1017
			/* Currency symbol */
			if (format[1] == '$') {
1018
				gboolean no_locale = TRUE;
1019
				for (format += 2; *format && *format != ']' ; ++format)
1020 1021 1022 1023 1024
					/* strip digits from [$<currency>-{digit}+] */
					if (*format == '-')
						no_locale = FALSE;
					else if (no_locale)
						g_string_append_c (result, *format);
Jody Goldberg's avatar
Jody Goldberg committed
1025 1026
				if (!*format)
					--format;
1027
			} else if (!ignore_further_elapsed)
1028 1029
				time_display_elapsed = TRUE;
			break;
Jody Goldberg's avatar
Jody Goldberg committed
1030

1031
		case '#':
Jody Goldberg's avatar
Jody Goldberg committed
1032
			can_render_number = TRUE;
1033 1034 1035
			if (info.decimal_separator_seen)
				info.right_optional++;
			break;
Arturo Espinosa's avatar
Arturo Espinosa committed
1036

1037
		case '?':
Jody Goldberg's avatar
Jody Goldberg committed
1038
			can_render_number = TRUE;
1039 1040 1041 1042 1043
			if (info.decimal_separator_seen)
				info.right_spaces++;
			else
				info.left_spaces++;
			break;
Arturo Espinosa's avatar
Arturo Espinosa committed
1044

1045
		case '0':
Jody Goldberg's avatar
Jody Goldberg committed
1046
			can_render_number = TRUE;
1047 1048 1049 1050 1051 1052 1053 1054 1055
			if (info.decimal_separator_seen){
				info.right_req++;
				info.right_allowed++;
				info.right_spaces++;
			} else {
				info.left_spaces++;
				info.left_req++;
			}
			break;
Arturo Espinosa's avatar
Arturo Espinosa committed
1056

Jody Goldberg's avatar
Jody Goldberg committed
1057
		case '.': {
Morten Welinder's avatar
Morten Welinder committed
1058
			int c = *(format + 1);
Arturo Espinosa's avatar
Arturo Espinosa committed
1059

Jody Goldberg's avatar
Jody Goldberg committed
1060
			can_render_number = TRUE;
Morten Welinder's avatar
Morten Welinder committed
1061 1062 1063 1064 1065 1066 1067
			if (c && (c != '0' && c != '#' && c != '?'))
				number /= 1000;
			else
				info.decimal_separator_seen = TRUE;
			break;
		}

1068 1069 1070 1071 1072 1073 1074 1075
		case ',':
			if (can_render_number) {
				guchar const *tmp = format;
				while (*++tmp == ',')
					;
				if (*tmp == '\0' || *tmp == '.' || *tmp == ';')
					/* NOTE : format-tmp is NEGATIVE */
					info.scale = gpow10 (3*(format-tmp));
1076
				info.group_thousands = TRUE;
1077 1078 1079
				format = tmp-1;
			} else
				g_string_append_c (result, format_get_thousand ());
Morten Welinder's avatar
Morten Welinder committed
1080
			break;
Arturo Espinosa's avatar
Arturo Espinosa committed
1081

1082 1083 1084 1085 1086 1087 1088
		/* FIXME: this is a gross hack */
		case 'E': case 'e': {
			gboolean const is_lower = (*format == 'e');
			gboolean shows_plus = FALSE;