1 /*
2  * gutils.c: Various utility routines that do not depend on the GUI of Gnumeric
3  *
4  * Authors:
5  *    Miguel de Icaza (miguel@gnu.org)
6  *    Jukka-Pekka Iivonen (iivonen@iki.fi)
7  *    Zbigniew Chyla (cyba@gnome.pl)
8  */
9 #include <gnumeric-config.h>
10 #include <glib/gi18n-lib.h>
11 #include <gnumeric.h>
12 #include <gutils.h>
13 #include <gnumeric-paths.h>
14 
15 #include <sheet.h>
16 #include <ranges.h>
17 #include <mathfunc.h>
18 #include <workbook-view.h>
19 #include <workbook.h>
20 #include <workbook-priv.h>
21 
22 #include <goffice/goffice.h>
23 
24 #include <stdlib.h>
25 #include <math.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <locale.h>
32 #include <gsf/gsf-impl-utils.h>
33 #include <gsf/gsf-doc-meta-data.h>
34 #include <gsf/gsf-timestamp.h>
35 
36 #define SHEET_SELECTION_KEY "sheet-selection"
37 #define SSCONVERT_SHEET_SET_KEY "ssconvert-sheets"
38 
39 static char *gnumeric_lib_dir;
40 static char *gnumeric_data_dir;
41 static char *gnumeric_locale_dir;
42 static char *gnumeric_usr_dir;
43 static char *gnumeric_usr_dir_unversioned;
44 static char *gnumeric_extern_plugin_dir;
45 static GSList *gutils_xml_in_docs;
46 
47 static char *
running_in_tree(void)48 running_in_tree (void)
49 {
50 	const char *argv0 = g_get_prgname ();
51 
52 	if (!argv0)
53 		return NULL;
54 
55 	/* Look for ".libs" as final path element.  */
56 	{
57 		const char *dotlibs = strstr (argv0, ".libs/");
58 		if (dotlibs &&
59 		    (dotlibs == argv0 || G_IS_DIR_SEPARATOR (dotlibs[-1])) &&
60 		    strchr (dotlibs + 6, G_DIR_SEPARATOR) == NULL) {
61 			size_t l = dotlibs - argv0;
62 			char *res = g_strndup (argv0, l);
63 
64 			while (l > 0 && G_IS_DIR_SEPARATOR (res[l - 1]))
65 				res[--l] = 0;
66 			while (l > 0 && !G_IS_DIR_SEPARATOR (res[l - 1]))
67 				res[--l] = 0;
68 			while (l > 0 && G_IS_DIR_SEPARATOR (res[l - 1]))
69 				res[--l] = 0;
70 
71 			return res;
72 		}
73 	}
74 
75 	{
76 		const char *builddir = g_getenv ("GNM_TEST_TOP_BUILDDIR");
77 		if (builddir)
78 			return g_strdup (builddir);
79 	}
80 
81 	return NULL;
82 }
83 
84 static gboolean gutils_inited = FALSE;
85 
86 void
gutils_init(void)87 gutils_init (void)
88 {
89 	char const *home_dir;
90 	char *top_builddir;
91 
92 	// This function will end up being called twice in normal operation:
93 	// once from gnm_pre_parse_init and once from gnm_init.  Introspection
94 	// will not get the first.
95 	if (gutils_inited)
96 		return;
97 
98 #ifdef G_OS_WIN32
99 	gchar *dir = g_win32_get_package_installation_directory_of_module (NULL);
100 	gnumeric_lib_dir = g_build_filename (dir, "lib",
101 					     "gnumeric", GNM_VERSION_FULL,
102 					     NULL);
103 	gnumeric_data_dir = g_build_filename (dir, "share",
104 					      "gnumeric", GNM_VERSION_FULL,
105 					      NULL);
106 	gnumeric_locale_dir = g_build_filename (dir, "share", "locale", NULL);
107 	gnumeric_extern_plugin_dir = g_build_filename
108 		(dir, "lib", "gnumeric", GNM_API_VERSION, "plugins",
109 		 NULL);
110 	g_free (dir);
111 #else
112 	top_builddir = running_in_tree ();
113 	if (top_builddir) {
114 		gnumeric_lib_dir =
115 			go_filename_simplify (top_builddir, GO_DOTDOT_SYNTACTIC,
116 					      FALSE);
117 		if (gnm_debug_flag ("in-tree"))
118 			g_printerr ("Running in-tree [%s]\n", top_builddir);
119 		g_free (top_builddir);
120 	}
121 
122 	if (!gnumeric_lib_dir)
123 		gnumeric_lib_dir = g_strdup (GNUMERIC_LIBDIR);
124 	gnumeric_data_dir = g_strdup (GNUMERIC_DATADIR);
125 	gnumeric_locale_dir = g_strdup (GNUMERIC_LOCALEDIR);
126 	gnumeric_extern_plugin_dir = g_strdup (GNUMERIC_EXTERNPLUGINDIR);
127 #endif
128 	home_dir = g_get_home_dir ();
129 	gnumeric_usr_dir_unversioned = home_dir
130 		? g_build_filename (home_dir, ".gnumeric", NULL)
131 		: NULL;
132 	gnumeric_usr_dir = gnumeric_usr_dir_unversioned
133 		? g_build_filename (gnumeric_usr_dir_unversioned, GNM_VERSION_FULL, NULL)
134 		: NULL;
135 
136 	gutils_inited = TRUE;
137 }
138 
139 void
gutils_shutdown(void)140 gutils_shutdown (void)
141 {
142 	GSList *l;
143 
144 	g_free (gnumeric_lib_dir);
145 	gnumeric_lib_dir = NULL;
146 	g_free (gnumeric_data_dir);
147 	gnumeric_data_dir = NULL;
148 	g_free (gnumeric_locale_dir);
149 	gnumeric_locale_dir = NULL;
150 	g_free (gnumeric_usr_dir);
151 	gnumeric_usr_dir = NULL;
152 	g_free (gnumeric_usr_dir_unversioned);
153 	gnumeric_usr_dir_unversioned = NULL;
154 	g_free (gnumeric_extern_plugin_dir);
155 	gnumeric_extern_plugin_dir = NULL;
156 
157 	for (l = gutils_xml_in_docs; l; l = l->next) {
158 		GsfXMLInDoc **pdoc = l->data;
159 		gsf_xml_in_doc_free (*pdoc);
160 		*pdoc = NULL;
161 	}
162 	g_slist_free (gutils_xml_in_docs);
163 	gutils_xml_in_docs = NULL;
164 }
165 
166 char const *
gnm_sys_lib_dir(void)167 gnm_sys_lib_dir (void)
168 {
169 	return gnumeric_lib_dir;
170 }
171 
172 char const *
gnm_sys_data_dir(void)173 gnm_sys_data_dir (void)
174 {
175 	return gnumeric_data_dir;
176 }
177 
178 char const *
gnm_sys_extern_plugin_dir(void)179 gnm_sys_extern_plugin_dir (void)
180 {
181 	return gnumeric_extern_plugin_dir;
182 }
183 
184 char const *
gnm_locale_dir(void)185 gnm_locale_dir (void)
186 {
187 	return gnumeric_locale_dir;
188 }
189 
190 char const *
gnm_usr_dir(gboolean versioned)191 gnm_usr_dir (gboolean versioned)
192 {
193 	return versioned ? gnumeric_usr_dir : gnumeric_usr_dir_unversioned;
194 }
195 
196 
197 static gboolean
all_ascii(const char * s)198 all_ascii (const char *s)
199 {
200 	while ((guchar)*s < 0x7f) {
201 		if (*s)
202 			s++;
203 		else
204 			return TRUE;
205 	}
206 	return FALSE;
207 }
208 
209 /*
210  * Like strto[ld], but...
211  * 1. handles non-ascii characters
212  * 2. disallows 0x000.0p+00 and 0.0d+00
213  * 3. ensures sane errno on exit
214  */
215 gnm_float
gnm_utf8_strto(const char * s,char ** end)216 gnm_utf8_strto (const char *s, char **end)
217 {
218 	const char *p;
219 	int sign;
220 	char *dummy_end;
221 	GString *ascii;
222 	GString const *decimal = go_locale_get_decimal ();
223 	gboolean seen_decimal = FALSE;
224 	gboolean seen_digit = FALSE;
225 	size_t spaces = 0;
226 	gnm_float res;
227 	int save_errno;
228 
229 	if (all_ascii (s)) {
230 		res = gnm_strto (s, end);
231 		goto handle_denormal;
232 	}
233 
234 	ascii = g_string_sized_new (100);
235 
236 	if (!end)
237 		end = &dummy_end;
238 
239 	p = s;
240 	while (g_unichar_isspace (g_utf8_get_char (p))) {
241 		p = g_utf8_next_char (p);
242 		spaces++;
243 	}
244 
245 	sign = go_unichar_issign (g_utf8_get_char (p));
246 	if (sign) {
247 		g_string_append_c (ascii, "-/+"[sign + 1]);
248 		p = g_utf8_next_char (p);
249 	}
250 
251 	do {
252 		if (strncmp (p, decimal->str, decimal->len) == 0) {
253 			if (seen_decimal)
254 				break;
255 			seen_decimal = TRUE;
256 			go_string_append_gstring (ascii, decimal);
257 			p += decimal->len;
258 		} else if (g_unichar_isdigit (g_utf8_get_char (p))) {
259 			g_string_append_c (ascii, '0' + g_unichar_digit_value (g_utf8_get_char (p)));
260 			p = g_utf8_next_char (p);
261 			seen_digit = TRUE;
262 		} else
263 			break;
264 	} while (1);
265 
266 	if (!seen_digit) {
267 		/* No conversion, bail to gnm_strto for nan etc. */
268 		g_string_free (ascii, TRUE);
269 		return gnm_strto (s, end);
270 	}
271 
272 	if (*p == 'e' || *p == 'E') {
273 		int sign;
274 
275 		g_string_append_c (ascii, 'e');
276 		p = g_utf8_next_char (p);
277 
278 		sign = go_unichar_issign (g_utf8_get_char (p));
279 		if (sign) {
280 			g_string_append_c (ascii, "-/+"[sign + 1]);
281 			p = g_utf8_next_char (p);
282 		}
283 		while (g_unichar_isdigit (g_utf8_get_char (p))) {
284 			g_string_append_c (ascii, '0' + g_unichar_digit_value (g_utf8_get_char (p)));
285 			p = g_utf8_next_char (p);
286 		}
287 	}
288 
289 	res = gnm_strto (ascii->str, end);
290 	save_errno = errno;
291 	*end = g_utf8_offset_to_pointer
292 		(s, spaces + g_utf8_pointer_to_offset (ascii->str, *end));
293 	g_string_free (ascii, TRUE);
294 
295 	errno = save_errno;
296 
297 handle_denormal:
298 	save_errno = errno;
299 	if (res != 0 && gnm_abs (res) < GNM_MIN)
300 		errno = 0;
301 	else
302 		errno = save_errno;
303 
304 	return res;
305 }
306 
307 /*
308  * Like strtol, but...
309  * 1. handles non-ascii characters
310  * 2. assumes base==10
311  * 3. ensures sane errno on exit
312  */
313 long
gnm_utf8_strtol(const char * s,char ** end)314 gnm_utf8_strtol (const char *s, char **end)
315 {
316 	const char *p;
317 	int sign;
318 	char *dummy_end;
319 	unsigned long res = 0, lim, limd;
320 
321 	if (!end)
322 		end = &dummy_end;
323 
324 	p = s;
325 	while (g_unichar_isspace (g_utf8_get_char (p)))
326 		p = g_utf8_next_char (p);
327 
328 	sign = go_unichar_issign (g_utf8_get_char (p));
329 	if (sign)
330 		p = g_utf8_next_char (p);
331 	if (sign < 0) {
332 		lim = (-(unsigned long)LONG_MIN) / 10u;
333 		limd = (-(unsigned long)LONG_MIN) % 10u;
334 	} else {
335 		lim = (unsigned long)LONG_MAX / 10u;
336 		limd = (unsigned long)LONG_MAX % 10u;
337 	}
338 
339 	if (!g_unichar_isdigit (g_utf8_get_char (p))) {
340 		errno = 0;
341 		*end = (char *)s;
342 		return 0;
343 	}
344 
345 	while (g_unichar_isdigit (g_utf8_get_char (p))) {
346 		guint8 dig = g_unichar_digit_value (g_utf8_get_char (p));
347 		p = g_utf8_next_char (p);
348 
349 		if (res > lim || (res == lim && dig > limd)) {
350 			/* Overflow */
351 			while (g_unichar_isdigit (g_utf8_get_char (p)))
352 				p = g_utf8_next_char (p);
353 			*end = (char *)p;
354 			errno = ERANGE;
355 			return sign < 0 ? LONG_MIN : LONG_MAX;
356 		}
357 
358 		res = res * 10u + dig;
359 	}
360 	*end = (char *)p;
361 	errno = 0;
362 	return sign < 0 ? (long)-res : (long)res;
363 }
364 
365 
366 int
gnm_regcomp_XL(GORegexp * preg,char const * pattern,int cflags,gboolean anchor_start,gboolean anchor_end)367 gnm_regcomp_XL (GORegexp *preg, char const *pattern, int cflags,
368 		gboolean anchor_start, gboolean anchor_end)
369 {
370 	GString *res = g_string_new (NULL);
371 	int retval;
372 
373 	if (anchor_start)
374 		g_string_append_c (res, '^');
375 
376 	while (*pattern) {
377 		switch (*pattern) {
378 		case '*':
379 			g_string_append (res, ".*");
380 			pattern++;
381 			break;
382 
383 		case '?':
384 			g_string_append_c (res, '.');
385 			pattern++;
386 			break;
387 
388 		case '~':
389 			if (pattern[1] == '*' ||
390 			    pattern[1] == '?' ||
391 			    pattern[1] == '~')
392 				pattern++;
393 			/* Fall through */
394 		default:
395 			pattern = go_regexp_quote1 (res, pattern);
396 		}
397 	}
398 
399 	if (anchor_end)
400 		g_string_append_c (res, '$');
401 
402 	retval = go_regcomp (preg, res->str, cflags);
403 	g_string_free (res, TRUE);
404 	return retval;
405 }
406 
407 /**
408  * gnm_excel_search_impl:
409  * @needle: the pattern to search for, see gnm_regcomp_XL.
410  * @haystack: the string to search in.
411  * @skip: zero-based search start point in characters.
412  *
413  * Returns: -1 for a non-match, or zero-based location in
414  * characters.
415  *
416  * The is the implementation of Excel's SEARCH function.
417  * However, note that @skip and return value are zero-based.
418  */
419 int
gnm_excel_search_impl(const char * needle,const char * haystack,size_t skip)420 gnm_excel_search_impl (const char *needle, const char *haystack,
421 		       size_t skip)
422 {
423 	const char *hay2;
424 	size_t i;
425 	GORegexp r;
426 
427 	for (i = skip, hay2 = haystack; i > 0; i--) {
428 		if (*hay2 == 0)
429 			return -1;
430 		hay2 = g_utf8_next_char (hay2);
431 	}
432 
433 	if (gnm_regcomp_XL (&r, needle, GO_REG_ICASE, FALSE, FALSE) == GO_REG_OK) {
434 		GORegmatch rm;
435 
436 		switch (go_regexec (&r, hay2, 1, &rm, 0)) {
437 		case GO_REG_NOMATCH:
438 			break;
439 		case GO_REG_OK:
440 			go_regfree (&r);
441 			return skip +
442 				g_utf8_pointer_to_offset (hay2, hay2 + rm.rm_so);
443 		default:
444 			g_warning ("Unexpected go_regexec result");
445 		}
446 		go_regfree (&r);
447 	} else {
448 		g_warning ("Unexpected regcomp result");
449 	}
450 
451 	return -1;
452 }
453 
454 
455 #if 0
456 static char const *
457 color_to_string (PangoColor color)
458 {
459 	static char result[100];
460 	sprintf (result, "%04x:%04x:%04x", color.red, color.green, color.blue);
461 	return result;
462 }
463 
464 static const char *
465 enum_name (GType typ, int i)
466 {
467 	static char result[100];
468 	GEnumClass *ec = g_type_class_ref (typ);
469 
470 	if (ec) {
471 		GEnumValue *ev = g_enum_get_value (ec, i);
472 		g_type_class_unref (ec);
473 
474 		if (ev && ev->value_nick)
475 			return ev->value_nick;
476 		if (ev && ev->value_name)
477 			return ev->value_name;
478 	}
479 
480 	sprintf (result, "%d", i);
481 	return result;
482 }
483 
484 static gboolean
485 cb_gnm_pango_attr_dump (PangoAttribute *attr, gpointer user_data)
486 {
487 	g_print ("  start=%u; end=%u\n", attr->start_index, attr->end_index);
488 	switch (attr->klass->type) {
489 	case PANGO_ATTR_FAMILY:
490 		g_print ("    family=\"%s\"\n", ((PangoAttrString *)attr)->value);
491 		break;
492 	case PANGO_ATTR_LANGUAGE:
493 		g_print ("    language=\"%s\"\n", pango_language_to_string (((PangoAttrLanguage *)attr)->value));
494 		break;
495 	case PANGO_ATTR_STYLE:
496 		g_print ("    style=%s\n",
497 			 enum_name (PANGO_TYPE_STYLE, ((PangoAttrInt *)attr)->value));
498 		break;
499 	case PANGO_ATTR_WEIGHT:
500 		g_print ("    weight=%s\n",
501 			 enum_name (PANGO_TYPE_WEIGHT, ((PangoAttrInt *)attr)->value));
502 		break;
503 	case PANGO_ATTR_VARIANT:
504 		g_print ("    variant=%s\n",
505 			 enum_name (PANGO_TYPE_VARIANT, ((PangoAttrInt *)attr)->value));
506 		break;
507 	case PANGO_ATTR_STRETCH:
508 		g_print ("    stretch=%s\n",
509 			 enum_name (PANGO_TYPE_STRETCH, ((PangoAttrInt *)attr)->value));
510 		break;
511 	case PANGO_ATTR_UNDERLINE:
512 		g_print ("    underline=%s\n",
513 			 enum_name (PANGO_TYPE_UNDERLINE, ((PangoAttrInt *)attr)->value));
514 		break;
515 	case PANGO_ATTR_STRIKETHROUGH:
516 		g_print ("    strikethrough=%d\n", ((PangoAttrInt *)attr)->value);
517 		break;
518 	case PANGO_ATTR_RISE:
519 		g_print ("    rise=%d\n", ((PangoAttrInt *)attr)->value);
520 		break;
521 	case PANGO_ATTR_FALLBACK:
522 		g_print ("    fallback=%d\n", ((PangoAttrInt *)attr)->value);
523 		break;
524 	case PANGO_ATTR_LETTER_SPACING:
525 		g_print ("    letter_spacing=%d\n", ((PangoAttrInt *)attr)->value);
526 		break;
527 	case PANGO_ATTR_SIZE:
528 		g_print ("    size=%d%s\n",
529 			 ((PangoAttrSize *)attr)->size,
530 			 ((PangoAttrSize *)attr)->absolute ? " abs" : "");
531 		break;
532 	case PANGO_ATTR_SCALE:
533 		g_print ("    scale=%g\n", ((PangoAttrFloat *)attr)->value);
534 		break;
535 	case PANGO_ATTR_FOREGROUND:
536 		g_print ("    foreground=%s\n", color_to_string (((PangoAttrColor *)attr)->color));
537 		break;
538 	case PANGO_ATTR_BACKGROUND:
539 		g_print ("    background=%s\n", color_to_string (((PangoAttrColor *)attr)->color));
540 		break;
541 	case PANGO_ATTR_UNDERLINE_COLOR:
542 		g_print ("    underline_color=%s\n", color_to_string (((PangoAttrColor *)attr)->color));
543 		break;
544 	case PANGO_ATTR_STRIKETHROUGH_COLOR:
545 		g_print ("    strikethrough_color=%s\n", color_to_string (((PangoAttrColor *)attr)->color));
546 		break;
547 	case PANGO_ATTR_FONT_DESC: {
548 		char *desc = pango_font_description_to_string (((PangoAttrFontDesc*)attr)->desc);
549 		g_print  ("    font=\"%s\"\n", desc);
550 		g_free (desc);
551 		break;
552 	}
553 	default:
554 		g_print ("    type=%s\n", enum_name (PANGO_TYPE_ATTR_TYPE, attr->klass->type));
555 	}
556 
557 	return FALSE;
558 }
559 
560 void
561 gnm_pango_attr_dump (PangoAttrList *list)
562 {
563 	g_print ("PangoAttrList at %p\n", list);
564 	pango_attr_list_filter (list, cb_gnm_pango_attr_dump, NULL);
565 }
566 #endif
567 
568 
569 static gboolean
cb_gnm_pango_attr_list_equal(PangoAttribute * a,gpointer _sl)570 cb_gnm_pango_attr_list_equal (PangoAttribute *a, gpointer _sl)
571 {
572 	GSList **sl = _sl;
573 	*sl = g_slist_prepend (*sl, a);
574 	return FALSE;
575 }
576 
577 /*
578  * This is a bit of a hack.  It might claim a difference even when things
579  * actually are equal.  But not the other way around.
580  */
581 gboolean
gnm_pango_attr_list_equal(PangoAttrList const * l1,PangoAttrList const * l2)582 gnm_pango_attr_list_equal (PangoAttrList const *l1, PangoAttrList const *l2)
583 {
584 	if (l1 == l2)
585 		return TRUE;
586 	else if (l1 == NULL || l2 == NULL)
587 		return FALSE;
588 	else {
589 		gboolean res;
590 		GSList *sl1 = NULL, *sl2 = NULL;
591 		(void)pango_attr_list_filter ((PangoAttrList *)l1,
592 					      cb_gnm_pango_attr_list_equal,
593 					      &sl1);
594 		(void)pango_attr_list_filter ((PangoAttrList *)l2,
595 					      cb_gnm_pango_attr_list_equal,
596 					      &sl2);
597 
598 		while (sl1 && sl2) {
599 			const PangoAttribute *a1 = sl1->data;
600 			const PangoAttribute *a2 = sl2->data;
601 			if (a1->start_index != a2->start_index ||
602 			    a1->end_index != a2->end_index ||
603 			    !pango_attribute_equal (a1, a2))
604 				break;
605 			sl1 = g_slist_delete_link (sl1, sl1);
606 			sl2 = g_slist_delete_link (sl2, sl2);
607 		}
608 
609 		res = (sl1 == sl2);
610 		g_slist_free (sl1);
611 		g_slist_free (sl2);
612 		return res;
613 	}
614 }
615 
616 /* ------------------------------------------------------------------------- */
617 
618 struct _GnmLocale {
619 	char *num_locale;
620 	char *monetary_locale;
621 };
622 /**
623  * gnm_push_C_locale: (skip)
624  *
625  * Returns the current locale, and sets the locale and the value-format
626  * engine's locale to 'C'.  The caller must call gnm_pop_C_locale to free the
627  * result and restore the previous locale.
628  **/
629 GnmLocale *
gnm_push_C_locale(void)630 gnm_push_C_locale (void)
631 {
632 	GnmLocale *old = g_new0 (GnmLocale, 1);
633 
634 	old->num_locale = g_strdup (go_setlocale (LC_NUMERIC, NULL));
635 	go_setlocale (LC_NUMERIC, "C");
636 	old->monetary_locale = g_strdup (go_setlocale (LC_MONETARY, NULL));
637 	go_setlocale (LC_MONETARY, "C");
638 	go_locale_untranslated_booleans ();
639 
640 	return old;
641 }
642 
643 /**
644  * gnm_pop_C_locale: (skip)
645  * @locale: #GnmLocale
646  *
647  * Frees the result of gnm_push_C_locale and restores the original locale.
648  **/
649 void
gnm_pop_C_locale(GnmLocale * locale)650 gnm_pop_C_locale (GnmLocale *locale)
651 {
652 	/* go_setlocale restores bools to locale translation */
653 	go_setlocale (LC_MONETARY, locale->monetary_locale);
654 	g_free (locale->monetary_locale);
655 	go_setlocale (LC_NUMERIC, locale->num_locale);
656 	g_free (locale->num_locale);
657 	g_free (locale);
658 }
659 
660 /* ------------------------------------------------------------------------- */
661 
662 gboolean
gnm_debug_flag(const char * flag)663 gnm_debug_flag (const char *flag)
664 {
665 	GDebugKey key;
666 	key.key = (char *)flag;
667 	key.value = 1;
668 
669 	return g_parse_debug_string (g_getenv ("GNM_DEBUG"), &key, 1) != 0;
670 }
671 
672 /* ------------------------------------------------------------------------- */
673 
674 void
gnm_string_add_number(GString * buf,gnm_float d)675 gnm_string_add_number (GString *buf, gnm_float d)
676 {
677 	size_t old_len = buf->len;
678 	double d2;
679 	static int digits;
680 
681 	if (digits == 0) {
682 		gnm_float l10 = gnm_log10 (FLT_RADIX);
683 		digits = (int)gnm_ceil (GNM_MANT_DIG * l10) +
684 			(l10 == (int)l10 ? 0 : 1);
685 	}
686 
687 	g_string_append_printf (buf, "%.*" GNM_FORMAT_g, digits - 1, d);
688 	d2 = gnm_strto (buf->str + old_len, NULL);
689 
690 	if (d != d2) {
691 		g_string_truncate (buf, old_len);
692 		g_string_append_printf (buf, "%.*" GNM_FORMAT_g, digits, d);
693 	}
694 }
695 
696 /* ------------------------------------------------------------------------- */
697 
698 void
gnm_insert_meta_date(GODoc * doc,char const * name)699 gnm_insert_meta_date (GODoc *doc, char const *name)
700 {
701 	GValue *value = g_new0 (GValue, 1);
702 	GsfTimestamp *ts = gsf_timestamp_new ();
703 
704 	gsf_timestamp_set_time (ts, g_get_real_time () / 1000000);
705 	g_value_init (value, GSF_TIMESTAMP_TYPE);
706 	gsf_timestamp_to_value (ts, value);
707 	gsf_timestamp_free (ts);
708 
709 	gsf_doc_meta_data_insert (go_doc_get_meta_data (doc),
710 				  g_strdup (name),
711 				  value);
712 }
713 
714 /* ------------------------------------------------------------------------- */
715 
716 /**
717  * gnm_object_get_bool:
718  * @o: #GObject
719  * @name: property name
720  *
721  * Returns: the value of @o's boolean property @name.
722  */
723 gboolean
gnm_object_get_bool(gpointer o,const char * name)724 gnm_object_get_bool (gpointer o, const char *name)
725 {
726 	gboolean b;
727 	g_object_get (o, name, &b, NULL);
728 	return b;
729 }
730 
731 /**
732  * gnm_object_has_readable_prop:
733  * @obj: #GObject
734  * @property: property name
735  * @typ: property's type or %G_TYPE_NONE.  (Exact type, not is-a.)
736  * @pres: (out) (optional): location to store property value.
737  *
738  * Returns: %TRUE if @obj has a readable property named @property
739  * of type @typ.
740  */
741 gboolean
gnm_object_has_readable_prop(gconstpointer obj,const char * property,GType typ,gpointer pres)742 gnm_object_has_readable_prop (gconstpointer obj, const char *property,
743 			      GType typ, gpointer pres)
744 {
745 	GObjectClass *klass;
746 	GParamSpec *spec;
747 
748 	if (!obj)
749 		return FALSE;
750 
751 	klass =  G_OBJECT_GET_CLASS (G_OBJECT (obj));
752 	spec = g_object_class_find_property (klass, property);
753 	if (!spec ||
754 	    !(G_PARAM_READABLE & spec->flags) ||
755 	    (typ != G_TYPE_NONE && spec->value_type != typ))
756 		return FALSE;
757 
758 	if (pres)
759 		g_object_get (G_OBJECT (obj), property, pres, NULL);
760 	return TRUE;
761 }
762 
763 /* ------------------------------------------------------------------------- */
764 
765 gint
gnm_float_equal(gnm_float const * a,const gnm_float * b)766 gnm_float_equal (gnm_float const *a, const gnm_float *b)
767 {
768 	return (*a == *b);
769 }
770 
771 guint
gnm_float_hash(gnm_float const * d)772 gnm_float_hash (gnm_float const *d)
773 {
774 	int expt;
775 	gnm_float mant = gnm_frexp (gnm_abs (*d), &expt);
776 	guint h = ((guint)(0x80000000u * mant)) ^ expt;
777 	if (*d >= 0)
778 		h ^= 0x55555555;
779 	return h;
780 }
781 
782 /* ------------------------------------------------------------------------- */
783 
784 struct cb_compare {
785 	GnmHashTableOrder order;
786 	gpointer user;
787 };
788 
789 static gint
cb_compare(gconstpointer a_,gconstpointer b_,gpointer user_data)790 cb_compare (gconstpointer a_, gconstpointer b_, gpointer user_data)
791 {
792 	struct cb_compare *user = user_data;
793 	gpointer *a = (gpointer )a_;
794 	gpointer *b = (gpointer )b_;
795 
796 	return user->order (a[0], a[1], b[0], b[1], user->user);
797 }
798 
799 
800 /**
801  * gnm_hash_table_foreach_ordered:
802  * @h: Hash table
803  * @callback: (scope call): #GHFunc
804  * @order: (scope call): Ordering function
805  * @user: user data for callback and order
806  *
807  * Like g_hash_table_foreach, but with an ordering imposed.
808  **/
809 void
gnm_hash_table_foreach_ordered(GHashTable * h,GHFunc callback,GnmHashTableOrder order,gpointer user)810 gnm_hash_table_foreach_ordered (GHashTable *h,
811 				GHFunc callback,
812 				GnmHashTableOrder order,
813 				gpointer user)
814 {
815 	unsigned ui;
816 	GPtrArray *data;
817 	struct cb_compare u;
818 	GHashTableIter hiter;
819 	gpointer key, value;
820 
821 	/* Gather all key-value pairs */
822 	data = g_ptr_array_new ();
823 	g_hash_table_iter_init (&hiter, h);
824 	while (g_hash_table_iter_next (&hiter, &key, &value)) {
825 		g_ptr_array_add (data, key);
826 		g_ptr_array_add (data, value);
827 	}
828 
829 	/* Sort according to given ordering */
830 	u.order = order;
831 	u.user = user;
832 	g_qsort_with_data (data->pdata,
833 			   data->len / 2, 2 * sizeof (gpointer),
834 			   cb_compare,
835 			   &u);
836 
837 	/* Call user callback with all pairs */
838 	for (ui = 0; ui < data->len; ui += 2)
839 		callback (g_ptr_array_index (data, ui),
840 			  g_ptr_array_index (data, ui + 1),
841 			  user);
842 
843 	/* Clean up */
844 	g_ptr_array_free (data, TRUE);
845 }
846 
847 /* ------------------------------------------------------------------------- */
848 
849 void
gnm_xml_in_doc_dispose_on_exit(GsfXMLInDoc ** pdoc)850 gnm_xml_in_doc_dispose_on_exit (GsfXMLInDoc **pdoc)
851 {
852 	gutils_xml_in_docs = g_slist_prepend (gutils_xml_in_docs, pdoc);
853 }
854 
855 /**
856  * gnm_xml_out_end_element_check:
857  * @xout: #GsfXMLOut sink
858  * @id: expected tag being closed
859  *
860  * Closes an xml tag, expected it to be @id.  If it is not, tags will
861  * continue to be closed until the expected one is found in the hope
862  * that getting back to sync will make the result less corrupted.
863  */
864 void
gnm_xml_out_end_element_check(GsfXMLOut * xout,char const * id)865 gnm_xml_out_end_element_check (GsfXMLOut *xout, char const *id)
866 {
867 	while (TRUE) {
868 		const char *cid = gsf_xml_out_end_element (xout);
869 		if (!cid)
870 			return;
871 		if (g_str_equal (cid, id))
872 			return;
873 		g_critical ("Unbalanced xml tags while writing, please report");
874 	}
875 }
876 
877 
878 /* ------------------------------------------------------------------------- */
879 
880 /**
881  * gnm_file_saver_get_sheet:
882  * @fs: #GOFileSaver
883  * @wbv: #WorkbookView
884  *
885  * For a single-sheet saver, this function determines what sheet to save.
886  *
887  * Returns: (transfer none): the sheet to export
888  */
889 Sheet *
gnm_file_saver_get_sheet(GOFileSaver const * fs,WorkbookView const * wbv)890 gnm_file_saver_get_sheet (GOFileSaver const *fs, WorkbookView const *wbv)
891 {
892 	Workbook *wb;
893 	GPtrArray *sel;
894 
895 	g_return_val_if_fail (GO_IS_FILE_SAVER (fs), NULL);
896 	g_return_val_if_fail (go_file_saver_get_save_scope (fs) ==
897 			      GO_FILE_SAVE_SHEET, NULL);
898 	g_return_val_if_fail (GNM_IS_WORKBOOK_VIEW (wbv), NULL);
899 
900 	wb = wb_view_get_workbook (wbv);
901 
902 	sel = g_object_get_data (G_OBJECT (wb), SHEET_SELECTION_KEY);
903 	if (sel) {
904 		if (sel->len == 1)
905 			return g_ptr_array_index (sel, 0);
906 		g_critical ("Someone messed up sheet selection");
907 	}
908 
909 	return wb_view_cur_sheet (wbv);
910 }
911 
912 /**
913  * gnm_file_saver_get_sheets:
914  * @fs: #GOFileSaver
915  * @wbv: #WorkbookView
916  * @default_all: If %TRUE, all sheets will be selected by default; if %FALSE,
917  * this function will return %NULL if no sheets were explicitly selected.
918  *
919  * This function determines what sheets to save.
920  *
921  * Returns: (transfer container) (element-type Sheet): the sheets to export
922  *
923  * Note: the return value should be unreffed, not freed.
924  */
925 GPtrArray *
gnm_file_saver_get_sheets(GOFileSaver const * fs,WorkbookView const * wbv,gboolean default_all)926 gnm_file_saver_get_sheets (GOFileSaver const *fs,
927 			   WorkbookView const *wbv,
928 			   gboolean default_all)
929 {
930 	Workbook *wb;
931 	GPtrArray *sel, *sheets;
932 	GOFileSaveScope save_scope;
933 
934 	g_return_val_if_fail (GO_IS_FILE_SAVER (fs), NULL);
935 	g_return_val_if_fail (GNM_IS_WORKBOOK_VIEW (wbv), NULL);
936 
937 	save_scope = go_file_saver_get_save_scope (fs);
938 	wb = wb_view_get_workbook (wbv);
939 	sel = g_object_get_data (G_OBJECT (wb), SHEET_SELECTION_KEY);
940 	sheets = g_object_get_data (G_OBJECT (wb), SSCONVERT_SHEET_SET_KEY);
941 	if (sel)
942 		g_ptr_array_ref (sel);
943 	else if (sheets)
944 		sel = g_ptr_array_ref (sheets);
945 	else if (save_scope != GO_FILE_SAVE_WORKBOOK) {
946 		sel = g_ptr_array_new ();
947 		g_ptr_array_add (sel, wb_view_cur_sheet (wbv));
948 	} else if (default_all) {
949 		int i;
950 		sel = g_ptr_array_new ();
951 		for (i = 0; i < workbook_sheet_count (wb); i++) {
952 			Sheet *sheet = workbook_sheet_by_index (wb, i);
953 			g_ptr_array_add (sel, sheet);
954 		}
955 	}
956 
957 	return sel;
958 }
959 
960 gboolean
gnm_file_saver_common_export_option(GOFileSaver const * fs,Workbook const * wb,const char * key,const char * value,GError ** err)961 gnm_file_saver_common_export_option (GOFileSaver const *fs,
962 				     Workbook const *wb,
963 				     const char *key, const char *value,
964 				     GError **err)
965 {
966 	if (err)
967 		*err = NULL;
968 
969 	g_return_val_if_fail (GO_IS_FILE_SAVER (fs), FALSE);
970 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), FALSE);
971 	g_return_val_if_fail (key != NULL, FALSE);
972 	g_return_val_if_fail (value != NULL, FALSE);
973 
974 	if (strcmp (key, "sheet") == 0 ||
975 	    strcmp (key, "active-sheet") == 0) {
976 		GPtrArray *sheets;
977 		Sheet *sheet = NULL;
978 
979 		if (key[0] == 'a') {
980 			// Not ideal -- we lack a view here
981 			WORKBOOK_FOREACH_VIEW (wb, wbv, {
982 					sheet = wb_view_cur_sheet (wbv);
983 				});
984 		} else {
985 			sheet = workbook_sheet_by_name (wb, value);
986 		}
987 
988 		if (!sheet) {
989 			if (err)
990 				*err = g_error_new (go_error_invalid (), 0,
991 						    _("Unknown sheet \"%s\""),
992 						    value);
993 			return TRUE;
994 		}
995 
996 		sheets = g_object_get_data (G_OBJECT (wb), SSCONVERT_SHEET_SET_KEY);
997 		if (!sheets) {
998 			sheets = g_ptr_array_new ();
999 			g_object_set_data_full (G_OBJECT (wb),
1000 						SSCONVERT_SHEET_SET_KEY,
1001 						sheets,
1002 						(GDestroyNotify)g_ptr_array_unref);
1003 		}
1004 		g_ptr_array_add (sheets, sheet);
1005 
1006 		return FALSE;
1007 	}
1008 
1009 	if (err)
1010 		*err = g_error_new (go_error_invalid (), 0,
1011 				    _("Invalid export option \"%s\" for format %s"),
1012 				    key,
1013 				    go_file_saver_get_id (fs));
1014 
1015 	return TRUE;
1016 }
1017 
1018 
1019 static int
gnm_cpp_expr(const char * expr,G_GNUC_UNUSED GHashTable * vars)1020 gnm_cpp_expr (const char *expr, G_GNUC_UNUSED GHashTable *vars)
1021 {
1022 	int vmajor, vminor, vmicro;
1023 
1024 	while (g_ascii_isspace (*expr))
1025 		expr++;
1026 
1027 	if (sscanf (expr, "GTK_CHECK_VERSION (%d,%d,%d) ", &vmajor, &vminor, &vmicro) == 3) {
1028 		return gtk_check_version (vmajor, vminor, vmicro) ? 0 : 1;
1029 	}
1030 
1031 	g_warning ("Unhandled cpp expression %s", expr);
1032 	return 0;
1033 }
1034 
1035 
1036 char *
gnm_cpp(const char * src,GHashTable * vars)1037 gnm_cpp (const char *src, GHashTable *vars)
1038 {
1039 	GString *res = g_string_new (NULL);
1040 	GString *ifdefs = g_string_new ("1");
1041 
1042 	while (*src) {
1043 		const char *end;
1044 
1045 		end = strchr (src, '\n');
1046 		if (end)
1047 			end++;
1048 		else
1049 			end = src + strlen (src);
1050 
1051 		if (*src == '#') {
1052 			if (strncmp (src, "#ifdef ", 7) == 0 || strncmp (src, "#ifndef ", 8) == 0) {
1053 				int is_not = (src[3] == 'n');
1054 				const char *var = src + 7 + is_not;
1055 				char *w;
1056 				gboolean res;
1057 
1058 				while (g_ascii_isspace (*var))
1059 					var++;
1060 				src = var;
1061 				while (g_ascii_isalnum (*src))
1062 					src++;
1063 				w = g_strndup (var, src - var);
1064 				res = is_not ^ !!g_hash_table_lookup (vars, w);
1065 				g_string_append_c (ifdefs, ifdefs->str[ifdefs->len - 1] && res);
1066 				g_free (w);
1067 			} else if (strncmp (src, "#if ", 4) == 0) {
1068 				gboolean res = gnm_cpp_expr (src + 4, vars) > 0;
1069 				g_string_append_c (ifdefs, ifdefs->str[ifdefs->len - 1] && res);
1070 			} else if (strncmp (src, "#else", 5) == 0) {
1071 				ifdefs->str[ifdefs->len - 1] =
1072 					!ifdefs->str[ifdefs->len - 1] &&
1073 					ifdefs->str[ifdefs->len - 2];
1074 			} else if (strncmp (src, "#endif", 6) == 0 && ifdefs->len > 1) {
1075 				g_string_set_size (ifdefs, ifdefs->len - 1);
1076 			} else {
1077 				g_warning ("cpp failure");
1078 			}
1079 		} else {
1080 			if (ifdefs->str[ifdefs->len - 1])
1081 				g_string_append_len (res, src, end - src);
1082 		}
1083 		src = end;
1084 	}
1085 
1086 	g_string_free (ifdefs, TRUE);
1087 	return g_string_free (res, FALSE);
1088 }
1089