format.c 45.3 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 25
#include <gnumeric-config.h>
#include "gnumeric.h"
26
#include "numbers.h"
27
#include "format.h"
28 29

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

40
#include <libgnome/gnome-i18n.h>
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;
Jody Goldberg's avatar
Jody Goldberg committed
55
static gboolean date_order_cached = FALSE;
56

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

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

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

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

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

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

96 97 98 99 100 101
/**
 * 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
102 103 104
 *
 * Play with the default logic so that things come out nicely for the default
 * case.
105
 */
106
char const *
107
format_get_currency (gboolean *precedes, gboolean *space_sep)
108 109 110 111
{
	if (lc == NULL)
		lc = localeconv ();

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

119 120 121 122 123
	if (lc->currency_symbol == NULL || *lc->currency_symbol == '\0')
		return "$";
	return lc->currency_symbol;
}

Jody Goldberg's avatar
Jody Goldberg committed
124 125 126 127 128 129
/*
 * format_month_before_day :
 *
 * A quick utility routine to guess whether the default date format
 * uses day/month or month/day
 */
130 131 132
gboolean
format_month_before_day (void)
{
133
#ifdef HAVE_LANGINFO_H
Jody Goldberg's avatar
Jody Goldberg committed
134 135 136
	static gboolean month_first = TRUE;

	if (!date_order_cached) {
Morten Welinder's avatar
Morten Welinder committed
137
		char const *ptr = nl_langinfo (D_FMT);
Jody Goldberg's avatar
Jody Goldberg committed
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

		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;
153 154 155 156 157 158 159 160
#else
	static gboolean warning = TRUE;
	if (warning) {
		g_warning ("Incomplete locale library, dates will be month day year");
		warning = FALSE;
	}
	return TRUE;
#endif
161 162
}

Morten Welinder's avatar
Morten Welinder committed
163
/* Use comma as the arg separator unless the decimal point is a
Jody Goldberg's avatar
Jody Goldberg committed
164 165 166 167 168 169 170 171 172 173 174
 * comma, in which case use a semi-colon
 */
char
format_get_arg_sep (void)
{
	if (format_get_decimal () == ',')
		return ';';
	return ',';
}

char
175
format_get_col_sep (void)
Jody Goldberg's avatar
Jody Goldberg committed
176 177 178 179 180 181 182 183 184
{
	if (format_get_decimal () == ',')
		return '\\';
	return ',';
}
/***************************************************************************/

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

186
typedef struct {
187 188
        char const *format;
	gboolean    want_am_pm;
189
	gboolean    has_fraction;
190 191
        char        restriction_type;
        int         restriction_value;
Jody Goldberg's avatar
Jody Goldberg committed
192
	StyleColor *color;
193 194
} StyleFormatEntry;

Chris Lahey's avatar
Chris Lahey committed
195
/*
196 197 198 199 200 201 202 203 204 205 206
 * 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.
 *
207
 * A number specification is as described in the relevant portions of
208
 * the excel formatting information.  Commas can currently only appear
209 210
 * at the end of the number specification.  Fractions are supported
 * but the parsing is not as nice as it should be.
211
 */
212

213

214 215 216 217
/*
 * Parses the year field at the beginning of the format.  Returns the
 * number of characters used.
 */
218
static int
219
append_year (GString *string, const guchar *format, const struct tm *time_split)
220
{
Morten Welinder's avatar
Morten Welinder committed
221
	char temp[5];
Arturo Espinosa's avatar
Arturo Espinosa committed
222

Morten Welinder's avatar
Morten Welinder committed
223
	if (tolower (format[1]) != 'y'){
224 225 226
		g_string_append_c (string, 'y');
		return 1;
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
227

Morten Welinder's avatar
Morten Welinder committed
228
	if (tolower (format[2]) != 'y' || tolower (format[3]) != 'y'){
Miguel de Icaza's avatar
Miguel de Icaza committed
229
		sprintf (temp, "%02d", time_split->tm_year % 100);
230 231 232 233 234 235 236 237
		g_string_append (string, temp);
		return 2;
	}

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

	return 4;
238 239
}

240 241 242 243 244
/*
 * Parses the month field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
245
append_month (GString *string, int n, const struct tm *time_split)
246
{
Morten Welinder's avatar
Morten Welinder committed
247
	char temp[3];
Arturo Espinosa's avatar
Arturo Espinosa committed
248

249
	if (n == 1){
Morten Welinder's avatar
Morten Welinder committed
250
		sprintf (temp, "%d", time_split->tm_mon + 1);
251
		g_string_append (string, temp);
252 253
		return 1;
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
254

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

261
	if (n == 3){
Morten Welinder's avatar
Morten Welinder committed
262
		g_string_append (string, _(month_short[time_split->tm_mon]) + 1);
263 264 265
		return 3;
	}

Morten Welinder's avatar
Morten Welinder committed
266
	g_string_append (string, _(month_long[time_split->tm_mon]));
267
	return 4;
268 269
}

270 271 272 273 274
/*
 * Parses the day field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
275
append_day (GString *string, const guchar *format, const struct tm *time_split)
276
{
277 278
	char temp[3];

Morten Welinder's avatar
Morten Welinder committed
279
	if (tolower (format[1]) != 'd'){
280 281 282 283 284
		sprintf (temp, "%d", time_split->tm_mday);
		g_string_append (string, temp);
		return 1;
	}

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

Morten Welinder's avatar
Morten Welinder committed
291 292
	if (tolower (format[3]) != 'd'){
		g_string_append (string, _(day_short[time_split->tm_wday]) + 1);
293 294 295 296 297 298
		return 3;
	}

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

	return 4;
299 300
}

301
/*
302
 * Renders the hour.
303
 */
304
static void
Jody Goldberg's avatar
Jody Goldberg committed
305
append_hour (GString *string, int n, struct tm const *time_split,
306
	     gboolean want_am_pm)
307
{
308
	char *temp = g_alloca (n + 4 * sizeof (int));
309

310 311
	sprintf (temp, "%0*d", n,
		 want_am_pm
312
		 ? (((time_split->tm_hour + 11) % 12) + 1)
313
		 : time_split->tm_hour);
314 315 316 317 318 319 320
	g_string_append (string, temp);
}

/*
 * Renders the hour.
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
321
append_hour_elapsed (GString *string, struct tm const *time_split, int number)
322
{
Jody Goldberg's avatar
Jody Goldberg committed
323
	char buf[(DBL_MANT_DIG + DBL_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
324 325 326
	double hours = number * 24. + time_split->tm_hour;
	snprintf (buf, sizeof (buf), "%.0f", hours);
	g_string_append (string, buf);
327 328
}

329
/*
330
 * Renders the number of minutes.
331
 */
332
static void
Jody Goldberg's avatar
Jody Goldberg committed
333
append_minute (GString *string, int n, struct tm const *time_split)
334
{
335
	char *temp = g_alloca (n + 4 * sizeof (int));
336 337 338 339 340 341 342 343
	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
344
append_minute_elapsed (GString *string, struct tm const *time_split, int number)
345
{
Jody Goldberg's avatar
Jody Goldberg committed
346
	char buf[(DBL_MANT_DIG + DBL_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
347 348 349
	double minutes = ((number * 24.) + time_split->tm_hour) * 60. + time_split->tm_min;
	snprintf (buf, sizeof (buf), "%.0f", minutes);
	g_string_append (string, buf);
350 351 352 353 354 355
}

/*
 * Renders the second field.
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
356
append_second (GString *string, int n, struct tm const *time_split)
357
{
358
	char *temp = g_alloca (n + 4 * sizeof (int));
359 360 361 362 363
	sprintf (temp, "%0*d", n, time_split->tm_sec);
	g_string_append (string, temp);
}

/*
Jody Goldberg's avatar
Jody Goldberg committed
364
 * Renders the second field in elapsed
365 366
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
367
append_second_elapsed (GString *string, int n, struct tm const *time_split, int number)
368
{
Jody Goldberg's avatar
Jody Goldberg committed
369
	char buf[(DBL_MANT_DIG + DBL_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
370 371 372
	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);
373 374
}

Jody Goldberg's avatar
Jody Goldberg committed
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
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'.
393 394
 */
static void
Jody Goldberg's avatar
Jody Goldberg committed
395
format_entry_dtor (gpointer data, gpointer user_data)
396
{
Jody Goldberg's avatar
Jody Goldberg committed
397 398 399 400
	StyleFormatEntry *entry = data;
	if (entry->color != NULL) {
		style_color_unref (entry->color);
		entry->color = NULL;
401
	}
Jody Goldberg's avatar
Jody Goldberg committed
402 403
	g_free ((char *)entry->format);
	g_free (entry);
404 405
}

406 407
static void
format_entry_set_fmt (StyleFormatEntry *entry,
Jody Goldberg's avatar
Jody Goldberg committed
408 409
		      gchar const *begin,
		      gchar const *end)
410 411 412 413 414 415 416 417 418
{
	/* 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
419
static StyleColor * lookup_color (const char *str, const char *end);
Chris Lahey's avatar
Chris Lahey committed
420

421
/*
Jody Goldberg's avatar
Jody Goldberg committed
422 423 424 425
 * 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
426
 */
Jody Goldberg's avatar
Jody Goldberg committed
427
static void
428 429
format_compile (StyleFormat *format)
{
Jody Goldberg's avatar
Jody Goldberg committed
430
	gchar const *fmt, *real_start = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
431
	StyleFormatEntry *entry = format_entry_ctor ();
432 433
	int num_entries = 1, counter = 0;
	GSList *ptr;
Jody Goldberg's avatar
Jody Goldberg committed
434

435
	format_match_create (format);
Jody Goldberg's avatar
Jody Goldberg committed
436 437 438 439 440 441
	for (fmt = format->format; *fmt ; fmt++) {
		if (NULL == real_start && '[' != *fmt)
			real_start = fmt;

		switch (*fmt) {
		case '[': {
Jody Goldberg's avatar
Jody Goldberg committed
442 443
			gchar const *begin = fmt + 1;
			gchar const *end = begin;
Jody Goldberg's avatar
Jody Goldberg committed
444

445 446 447 448
			/* 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
449 450 451

			/* Check for conditional */
			if (*begin == '<') {
Morten Welinder's avatar
Morten Welinder committed
452
				if (begin[1] == '=') {
Jody Goldberg's avatar
Jody Goldberg committed
453 454
					entry->restriction_type = ',';
					begin += 2;
Morten Welinder's avatar
Morten Welinder committed
455
				} else if (begin[1] == '>') {
Jody Goldberg's avatar
Jody Goldberg committed
456 457 458 459 460 461 462
					entry->restriction_type = '+';
					begin += 2;
				} else {
					entry->restriction_type = '<';
					begin++;
				}
			} else if (*begin == '>') {
Morten Welinder's avatar
Morten Welinder committed
463
				if (begin[1] == '=') {
Jody Goldberg's avatar
Jody Goldberg committed
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
					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;
486
			}
Jody Goldberg's avatar
Jody Goldberg committed
487
			fmt = end;
488 489 490

			/* fall back on 0 for errors */
			errno = 0;
Morten Welinder's avatar
Morten Welinder committed
491
			entry->restriction_value = strtognum (begin, (char **)&end);
492 493
			if (errno == ERANGE || begin == end)
				entry->restriction_value = 0.;
494
			break;
Jody Goldberg's avatar
Jody Goldberg committed
495 496 497 498 499 500 501 502 503 504 505
		}

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

		case '\'' :
		case '\"' : {
			/* skip quoted strings */
			char const match = *fmt;
506 507
			for (; fmt[0] != match && fmt[1] != '\0'; fmt++)
				if (*fmt == '\\')
Jody Goldberg's avatar
Jody Goldberg committed
508
					fmt++;
509 510
			break;
		}
Chris Lahey's avatar
Chris Lahey committed
511

Jody Goldberg's avatar
Jody Goldberg committed
512
		case '/':
Morten Welinder's avatar
Morten Welinder committed
513
			if (fmt[1] == '?' || isdigit ((unsigned char)fmt[1])) {
Jody Goldberg's avatar
Jody Goldberg committed
514 515 516 517
				entry->has_fraction = TRUE;
				fmt++;
			}
			break;
Arturo Espinosa's avatar
Arturo Espinosa committed
518

Jody Goldberg's avatar
Jody Goldberg committed
519 520
		case 'a': case 'A':
		case 'p': case 'P':
Morten Welinder's avatar
Morten Welinder committed
521
			if (tolower (fmt[1]) == 'm')
Jody Goldberg's avatar
Jody Goldberg committed
522 523
				entry->want_am_pm = TRUE;
			break;
Miguel de Icaza's avatar
Miguel de Icaza committed
524

525 526
		case ';':
			format_entry_set_fmt (entry, real_start, fmt);
527
			format->entries = g_slist_append (format->entries, entry);
528 529 530 531 532 533
			num_entries++;

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

Jody Goldberg's avatar
Jody Goldberg committed
534 535 536
		default :
			break;
		}
537
	}
Miguel de Icaza's avatar
Miguel de Icaza committed
538

539
	format_entry_set_fmt (entry, real_start, fmt);
540 541 542 543 544 545 546 547 548
	format->entries = g_slist_append (format->entries, entry);

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

		if (entry->restriction_type == '*') {
			entry->restriction_value = 0.;

			switch (counter) {
549
			/* single conditions should remain catch alls */
Jody Goldberg's avatar
Jody Goldberg committed
550
			case 1 : if (num_entries > 1)
551
					 entry->restriction_type =
Jody Goldberg's avatar
Jody Goldberg committed
552
						 (num_entries > 2) ? '>' : '.';
553
				 break;
554 555 556 557
			/* Once there is more than one condition no catchall */
			case 2 : entry->restriction_type = '<'; break;
			case 3 : entry->restriction_type = '='; break;
			case 4 : entry->restriction_type = '@'; break;
558 559 560 561 562
			default :
				 break;
			}
		}
	}
Jody Goldberg's avatar
Jody Goldberg committed
563 564
}

Miguel de Icaza's avatar
Miguel de Icaza committed
565 566 567 568 569 570 571
/*
 * 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
 */
572 573 574
void
format_destroy (StyleFormat *format)
{
575 576
	g_slist_foreach (format->entries, &format_entry_dtor, NULL);
	g_slist_free (format->entries);
Jody Goldberg's avatar
Jody Goldberg committed
577
	format->entries = NULL;
578
	format_match_release (format);
579 580
}

581
static struct FormatColor {
582 583
	char       *name;
	StyleColor *color;
Morten Welinder's avatar
Morten Welinder committed
584
} format_colors[] = {
585 586 587 588 589 590 591 592 593
	{ N_("black")   },
	{ N_("blue")    },
	{ N_("cyan")    },
	{ N_("green")   },
	{ N_("magenta") },
	{ N_("red")     },
	{ N_("white")   },
	{ N_("yellow")  },
	{ NULL, NULL }
594 595
};

596 597 598 599 600
void
format_color_init (void)
{
	int i;

601 602 603
	for (i = 0; format_colors[i].name; i++)
		format_colors[i].color =
			style_color_new_name (format_colors[i].name);
604 605 606 607 608 609 610
}

void
format_color_shutdown (void)
{
	int i;

Morten Welinder's avatar
Morten Welinder committed
611 612
	for (i = 0; format_colors[i].name; i++)
		style_color_unref (format_colors[i].color);
613 614
}

615
static struct FormatColor const *
Jody Goldberg's avatar
Jody Goldberg committed
616
lookup_color_by_name (gchar const *str, gchar const *end,
617
		      gboolean const translate)
618
{
619
	int i, len;
620

621
	len = end - str;
Morten Welinder's avatar
Morten Welinder committed
622
	for (i = 0; format_colors[i].name; i++) {
Jody Goldberg's avatar
Jody Goldberg committed
623
		gchar const *name = format_colors[i].name;
624 625 626
		if (translate)
			name = _(name);

Morten Welinder's avatar
Morten Welinder committed
627
		if (0 == g_strncasecmp (name, str, len) && name[len] == '\0')
628 629 630 631 632 633
			return format_colors + i;
	}
	return NULL;
}

static StyleColor *
Jody Goldberg's avatar
Jody Goldberg committed
634
lookup_color (const gchar *str, const gchar *end)
635 636 637 638 639 640
{
	struct FormatColor const *color = lookup_color_by_name (str, end, FALSE);

	if (color != NULL) {
		style_color_ref (color->color);
		return color->color;
641 642 643 644
	}
	return NULL;
}

645
static gnum_float beyond_precision;
646
void
647
render_number (GString *result,
648
	       gnum_float number,
649
	       format_info_t const *info)
650
{
651
	char thousands_sep = format_get_thousand ();
Morten Welinder's avatar
Morten Welinder committed
652
	char num_buf[(GNUM_MANT_DIG + GNUM_MAX_EXP) * 2 + 1];
Jody Goldberg's avatar
Jody Goldberg committed
653
	gchar *num = num_buf + sizeof (num_buf) - 1;
654
	gnum_float frac_part, int_part;
655
	int group, zero_count, digit_count = 0;
656 657 658 659 660
	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;
661

662 663 664
	if (right_allowed >= 0 && !info->has_fraction) {
		/* Change "rounding" into "truncating".   */
		/* Note, that we assume number >= 0 here. */
665
		gnum_float delta = 5 * gpow10 (-right_allowed - 1);
666 667
		number += delta;
	}
Morten Welinder's avatar
Morten Welinder committed
668
	frac_part = modfgnum (gnumeric_add_epsilon (number), &int_part);
669

670
	*num = '\0';
671
	group = (info->group_thousands) ? 3 : -1;
672 673 674
	for (; int_part > beyond_precision ; int_part /= 10., digit_count++) {
		if (group-- == 0) {
			group = 2;
675
			*(--num) = thousands_sep;
676
		}
677
		*(--num) = '0';
678
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
679

680
	for (; int_part >= 1. ; int_part /= 10., digit_count++) {
681 682
		gnum_float r = floorgnum (int_part);
		int digit = r - floorgnum (r / 10) * 10;
683

684 685
		if (group-- == 0) {
			group = 2;
686 687 688 689
			*(--num) = thousands_sep;
		}
		*(--num) = digit + '0';
	}
690

691
	/* TODO : What ifthe only visible digits are zeros ? -0.00 looks bad */
692
	if (info->negative && !info->supress_minus)
693 694
		g_string_append_c (result, '-');
	if (left_req > digit_count) {
695
		for (left_spaces -= left_req ; left_spaces-- > 0 ;)
696 697 698 699 700 701
			g_string_append_c (result, ' ');
		for (left_req -= digit_count ; left_req-- > 0 ;)
			g_string_append_c (result, '0');
	}

	g_string_append (result, num);
702

703 704 705 706 707 708 709 710 711
	/* 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))
712
		g_string_append_c (result, format_get_decimal ());
713

714 715 716 717 718
	/* 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
719
		gint digit;
720 721 722
		frac_part *= 10.0;
		digit = (gint)frac_part;
		frac_part -= digit;
723
		g_string_append_c (result, digit + '0');
Arturo Espinosa's avatar
Arturo Espinosa committed
724 725 726 727
	}

	zero_count = 0;

728
	while (right_allowed-- > 0) {
Arturo Espinosa's avatar
Arturo Espinosa committed
729
		gint digit;
730 731 732
		frac_part *= 10.0;
		digit = (gint)frac_part;
		frac_part -= digit;
Arturo Espinosa's avatar
Arturo Espinosa committed
733

734
		if (digit == 0) {
Arturo Espinosa's avatar
Arturo Espinosa committed
735 736
			right_spaces -= zero_count + 1;
			zero_count = 0;
737 738
		} else
			zero_count ++;
Arturo Espinosa's avatar
Arturo Espinosa committed
739

740
		g_string_append_c (result, digit + '0');
Arturo Espinosa's avatar
Arturo Espinosa committed
741 742
	}

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

745
	while (right_spaces-- > 0)
746
		g_string_append_c (result, ' ');
747 748
}

749
static void
750
do_render_number (gnum_float number, format_info_t *info, GString *result)
751
{
752
	info->rendered = TRUE;
Chris Lahey's avatar
Chris Lahey committed
753

Arturo Espinosa's avatar
Arturo Espinosa committed
754
#if 0
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
	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
775

776
	render_number (result, number, info);
777 778
}

779 780 781 782 783 784 785
/*
 * 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
786
 * >
787 788 789
 * > 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
790
 * >
791 792 793 794
 * > 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.
 */
795
static struct tm *
796
split_time (gnum_float number)
797 798
{
	static struct tm tm;
799
	guint secs;
800
	GDate* date;
801

Morten Welinder's avatar
Morten Welinder committed
802
	date = datetime_serial_to_g (datetime_serial_raw_to_serial (number));
803
	g_date_to_struct_tm (date, &tm);
Morten Welinder's avatar
Morten Welinder committed
804
	g_date_free (date);
805

Morten Welinder's avatar
Morten Welinder committed
806
	secs = datetime_serial_raw_to_seconds (number);
807 808 809 810 811
	tm.tm_hour = secs / 3600;
	secs -= tm.tm_hour * 3600;
	tm.tm_min  = secs / 60;
	secs -= tm.tm_min * 60;
	tm.tm_sec  = secs;
812 813 814 815

	return &tm;
}

816 817 818 819
/*
 * Finds the decimal char in @str doing the proper parsing of a
 * format string
 */
820 821
static char const *
find_decimal_char (char const *str)
822 823
{
	for (;*str; str++){
Jody Goldberg's avatar
Jody Goldberg committed
824
		if (*str == '.')
825 826
			return str;

Jody Goldberg's avatar
Jody Goldberg committed
827
		if (*str == ',')
828 829 830 831 832
			continue;

		switch (*str){
			/* These ones do not have any argument */
		case '#': case '?': case '0': case '%':
Jody Goldberg's avatar
Jody Goldberg committed
833
		case '-': case '+': case ')': case '£':
Jody Goldberg's avatar
Jody Goldberg committed
834
		case ':': case '$': case '¥': case '¤':
835 836 837 838 839 840 841
		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
842
		case '"':
843 844 845 846 847 848
			for (str++; *str && *str != '"'; str++)
				;
			break;

			/* Escaped char and spacing format */
		case '\\': case '_':
Morten Welinder's avatar
Morten Welinder committed
849
			if (*(str + 1))
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
				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
878
format_remove_decimal (StyleFormat const *fmt)
879
{
880
	int offset = 1;
881
	char *ret, *p;
882
	char const *tmp;
Jody Goldberg's avatar
Jody Goldberg committed
883
	char const *format_string = fmt->format;
884 885

	/*
886 887 888 889
	 * 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.
890 891
	 */
	if (strcmp (format_string, "General") == 0)
892
		format_string = "0.########";
Jody Goldberg's avatar
Jody Goldberg committed
893

894 895
	tmp = find_decimal_char (format_string);
	if (!tmp)
896
		return NULL;
897

898 899
	ret = g_strdup (format_string);
	p = ret + (tmp - format_string);
900

901 902 903 904 905 906
	/* 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;
907 908
	else
		offset = 2;
909

910
	strcpy (p, p + offset);
911 912 913 914 915 916 917 918 919 920 921 922 923

	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
924
format_add_decimal (StyleFormat const *fmt)
925
{
926 927 928
	char const *pre = NULL;
	char const *post = NULL;
	char *res;
Jody Goldberg's avatar
Jody Goldberg committed
929
	char const *format_string = fmt->format;
930

931
	if (strcmp (format_string, "General") == 0) {
932
		format_string = "0";
Morten Welinder's avatar
Morten Welinder committed
933
		pre = format_string + 1;
934
		post = pre;
935
	} else {
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
		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
956
			post = pre + 1;
957
	}
958 959 960 961 962 963
	res = g_malloc ((pre - format_string + 1) +
		      1 + /* for the decimal */
		      1 + /* for the extra 0 */
		      strlen (post) +
		      1 /*terminate */);
	if (!res)
964 965
		return NULL;

966
	strncpy (res, format_string, pre - format_string);
Morten Welinder's avatar
Morten Welinder committed
967 968
	res[pre-format_string + 0] = '.';
	res[pre-format_string + 1] = '0';
969 970 971
	strcpy (res + (pre - format_string) + 2, post);

	return res;
972 973
}

Jody Goldberg's avatar
Jody Goldberg committed
974 975
/*********************************************************************/

976
static gchar *
977
format_number (gnum_float number, int col_width, StyleFormatEntry const *entry)
978 979
{
	GString *result = g_string_new ("");
Jody Goldberg's avatar
Jody Goldberg committed
980
	guchar const *format = (guchar *)(entry->format);