Branch data Line data Source code
1 : : /********************************************************************\
2 : : * gnc-ui-util.c -- utility functions for the GnuCash UI *
3 : : * Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
4 : : * *
5 : : * This program is free software; you can redistribute it and/or *
6 : : * modify it under the terms of the GNU General Public License as *
7 : : * published by the Free Software Foundation; either version 2 of *
8 : : * the License, or (at your option) any later version. *
9 : : * *
10 : : * This program is distributed in the hope that it will be useful, *
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 : : * GNU General Public License for more details. *
14 : : * *
15 : : * You should have received a copy of the GNU General Public License*
16 : : * along with this program; if not, contact: *
17 : : * *
18 : : * Free Software Foundation Voice: +1-617-542-5942 *
19 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
21 : : \********************************************************************/
22 : :
23 : : #include <config.h>
24 : :
25 : : #ifdef __MINGW32__
26 : : #define __USE_MINGW_ANSI_STDIO 1
27 : : #endif
28 : : #include "gnc-ui-util.h"
29 : : #include <glib.h>
30 : : #include <glib/gi18n.h>
31 : : #include <gio/gio.h>
32 : : #include <ctype.h>
33 : : #include <errno.h>
34 : : #include <limits.h>
35 : : #include <locale.h>
36 : : #include <math.h>
37 : : #if defined(G_OS_WIN32) && !defined(_MSC_VER)
38 : : # include <pow.h>
39 : : #endif
40 : : #include <stdarg.h>
41 : : #include <stdlib.h>
42 : : #include <stdio.h>
43 : : #include <string.h>
44 : : #include <cinttypes>
45 : : #include <unicode/uchar.h>
46 : : #include <unicode/utf8.h>
47 : : #include <unicode/listformatter.h>
48 : :
49 : : #include "qof.h"
50 : : #include "gnc-prefs.h"
51 : : #include "Account.h"
52 : : #include "Transaction.h"
53 : : #include "gnc-engine.h"
54 : : #include "gnc-features.h"
55 : : #include "gnc-hooks.h"
56 : : #include "gnc-session.h"
57 : : #include "engine-helpers.h"
58 : : #include "gnc-locale-utils.h"
59 : :
60 : : #define GNC_PREF_CURRENCY_CHOICE_LOCALE "currency-choice-locale"
61 : : #define GNC_PREF_CURRENCY_CHOICE_OTHER "currency-choice-other"
62 : : #define GNC_PREF_CURRENCY_OTHER "currency-other"
63 : : #define GNC_PREF_REVERSED_ACCTS_NONE "reversed-accounts-none"
64 : : #define GNC_PREF_REVERSED_ACCTS_CREDIT "reversed-accounts-credit"
65 : : #define GNC_PREF_REVERSED_ACCTS_INC_EXP "reversed-accounts-incomeexpense"
66 : : #define GNC_PREF_PRICES_FORCE_DECIMAL "force-price-decimal"
67 : :
68 : : using UniStr = icu::UnicodeString;
69 : :
70 : : static QofLogModule log_module = GNC_MOD_GUI;
71 : :
72 : : static bool auto_decimal_enabled = false;
73 : : static int auto_decimal_places = 2; /* default, can be changed */
74 : :
75 : : static bool reverse_balance_inited = false;
76 : : static bool reverse_type[NUM_ACCOUNT_TYPES];
77 : :
78 : : /* Cache currency ISO codes and only look them up in the settings when
79 : : * absolutely necessary. Can't cache a pointer to the data structure
80 : : * as that will change any time the book changes. */
81 : : static char* user_default_currency = nullptr;
82 : : static char* user_report_currency = nullptr;
83 : : constexpr int maximum_decimals = 15;
84 : : constexpr int64_t pow_10[] = {1, 10, 100, 1000, 10000, 100000, 1000000,
85 : : 10000000, 100000000, 1000000000, 10000000000,
86 : : 100000000000, 1000000000000, 10000000000000,
87 : : 100000000000000, 1000000000000000};
88 : :
89 : : char*
90 : 0 : gnc_normalize_account_separator (const gchar* separator)
91 : : {
92 : 0 : char* new_sep=nullptr;
93 : :
94 : 0 : if (!separator || !*separator || g_strcmp0(separator, "colon") == 0)
95 : 0 : new_sep = g_strdup (":");
96 : 0 : else if (g_strcmp0(separator, "slash") == 0)
97 : 0 : new_sep = g_strdup ("/");
98 : 0 : else if (g_strcmp0(separator, "backslash") == 0)
99 : 0 : new_sep = g_strdup ("\\");
100 : 0 : else if (g_strcmp0(separator, "dash") == 0)
101 : 0 : new_sep = g_strdup ("-");
102 : 0 : else if (g_strcmp0(separator, "period") == 0)
103 : 0 : new_sep = g_strdup (".");
104 : : else
105 : 0 : new_sep = g_strdup (separator);
106 : :
107 : 0 : return new_sep;
108 : : }
109 : : /********************************************************************\
110 : : * gnc_configure_account_separator *
111 : : * updates the current account separator character *
112 : : * *
113 : : * Args: none *
114 : : \*******************************************************************/
115 : : static void
116 : 0 : gnc_configure_account_separator (void)
117 : : {
118 : 0 : auto string = gnc_prefs_get_string(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNT_SEPARATOR);
119 : 0 : auto separator = gnc_normalize_account_separator (string);
120 : :
121 : 0 : gnc_set_account_separator(separator);
122 : :
123 : 0 : g_free(string);
124 : 0 : g_free(separator);
125 : 0 : }
126 : :
127 : :
128 : : static void
129 : 7 : gnc_configure_reverse_balance (void)
130 : : {
131 : 112 : for (auto i = 0; i < NUM_ACCOUNT_TYPES; i++)
132 : 105 : reverse_type[i] = false;
133 : :
134 : 7 : if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_INC_EXP))
135 : : {
136 : 0 : reverse_type[ACCT_TYPE_INCOME] = true;
137 : 0 : reverse_type[ACCT_TYPE_EXPENSE] = true;
138 : : }
139 : 7 : else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_CREDIT))
140 : : {
141 : 0 : reverse_type[ACCT_TYPE_LIABILITY] = true;
142 : 0 : reverse_type[ACCT_TYPE_PAYABLE] = true;
143 : 0 : reverse_type[ACCT_TYPE_EQUITY] = true;
144 : 0 : reverse_type[ACCT_TYPE_INCOME] = true;
145 : 0 : reverse_type[ACCT_TYPE_CREDIT] = true;
146 : : }
147 : 7 : else if (!gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_NONE))
148 : 7 : PWARN("no reversed account preference set, using none");
149 : :
150 : 7 : }
151 : :
152 : : static void
153 : 7 : gnc_reverse_balance_init (void)
154 : : {
155 : 7 : gnc_configure_reverse_balance ();
156 : 7 : reverse_balance_inited = TRUE;
157 : 7 : }
158 : :
159 : : gboolean
160 : 2204 : gnc_reverse_balance (const Account *account)
161 : : {
162 : 2204 : if (account == nullptr)
163 : 0 : return FALSE;
164 : :
165 : 2204 : auto type = xaccAccountGetType (account);
166 : 2204 : if ((type < 0) || (type >= NUM_ACCOUNT_TYPES))
167 : 0 : return FALSE;
168 : :
169 : 2204 : if (!reverse_balance_inited)
170 : 7 : gnc_reverse_balance_init ();
171 : :
172 : 2204 : return reverse_type[type];
173 : : }
174 : :
175 : 0 : gboolean gnc_using_equity_type_opening_balance_account (QofBook* book)
176 : : {
177 : 0 : return gnc_features_check_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
178 : : }
179 : :
180 : 0 : void gnc_set_use_equity_type_opening_balance_account (QofBook* book)
181 : : {
182 : 0 : gnc_features_set_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
183 : 0 : }
184 : :
185 : : char*
186 : 0 : gnc_get_default_directory (const char* section)
187 : : {
188 : 0 : auto dir = gnc_prefs_get_string (section, GNC_PREF_LAST_PATH);
189 : 0 : if (!(dir && *dir))
190 : : {
191 : 0 : g_free (dir); // if it's ""
192 : : #ifdef G_OS_WIN32
193 : : dir = g_strdup (g_get_user_data_dir ()); /* equivalent of "My Documents" */
194 : : #else
195 : 0 : dir = g_strdup (g_get_home_dir ());
196 : : #endif
197 : : }
198 : 0 : return dir;
199 : : }
200 : :
201 : : void
202 : 0 : gnc_set_default_directory (const char* section, const char* directory)
203 : : {
204 : 0 : gnc_prefs_set_string(section, GNC_PREF_LAST_PATH, directory);
205 : 0 : }
206 : :
207 : : QofBook *
208 : 10754 : gnc_get_current_book (void)
209 : : {
210 : 10754 : return qof_session_get_book (gnc_get_current_session ());
211 : : }
212 : :
213 : : /* If there is no current session, there is no book and we must be dealing
214 : : * with a new book. When gnucash is started with --nofile, there is
215 : : * initially no session (and no book), but by the time we check, one
216 : : * could have been created (for example, if an empty account tree tab is
217 : : * opened, a session is created which creates a new, but empty, book).
218 : : * A session is created and a book is loaded from a backend when gnucash is
219 : : * started with a file, but selecting 'new file' keeps a session open. So we
220 : : * need to check as well for a book with no accounts (root with no children). */
221 : : gboolean
222 : 0 : gnc_is_new_book (void)
223 : : {
224 : 0 : return (!gnc_current_session_exist() ||
225 : 0 : gnc_account_n_children (gnc_book_get_root_account (gnc_get_current_book ())) == 0);
226 : : }
227 : :
228 : : #define OPTION_TAXUS_NAME "tax_US/name"
229 : : #define OPTION_TAXUS_TYPE "tax_US/type"
230 : : #define OLD_OPTION_TAXUS_NAME "book/tax_US/name"
231 : : #define OLD_OPTION_TAXUS_TYPE "book/tax_US/type"
232 : :
233 : : void
234 : 0 : gnc_set_current_book_tax_name_type (gboolean name_changed, const char* tax_name,
235 : : gboolean type_changed, const char* tax_type)
236 : : {
237 : 0 : if (name_changed)
238 : : {
239 : 0 : if (type_changed)
240 : : {
241 : 0 : auto book = gnc_get_current_book();
242 : 0 : if ((g_strcmp0 (tax_name, "") == 0) ||
243 : : (tax_name == nullptr))
244 : : { /* change to no name */
245 : 0 : if ((g_strcmp0 (tax_type, "Other") == 0) ||
246 : 0 : (g_strcmp0 (tax_type, "") == 0) ||
247 : : (tax_type == nullptr))
248 : : { /* need to delete both name and type and the "tax_US" frame */
249 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
250 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
251 : 0 : qof_book_option_frame_delete(book, "tax_US");
252 : : }
253 : : else
254 : : { /* delete the name & change the type; keep the "tax_US" frame */
255 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
256 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
257 : : }
258 : : }
259 : : else /* new name */
260 : : {
261 : 0 : if ((g_strcmp0 (tax_type, "Other") == 0) ||
262 : 0 : (g_strcmp0 (tax_type, "") == 0) ||
263 : : (tax_type == nullptr))
264 : : { /* delete the type & change the name; keep the "tax_US" frame */
265 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
266 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
267 : : }
268 : : else /* and new type */
269 : : { /* change the name & change the type */
270 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
271 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
272 : : }
273 : : }
274 : : }
275 : : else /* no type change but name changed */
276 : : {
277 : 0 : auto book = gnc_get_current_book();
278 : 0 : if ((g_strcmp0 (tax_name, "") == 0) ||
279 : : (tax_name == nullptr))
280 : : { /* change to no name */
281 : 0 : if ((g_strcmp0 (tax_type, "Other") == 0) ||
282 : 0 : (g_strcmp0 (tax_type, "") == 0) ||
283 : : (tax_type == nullptr))
284 : : { /* delete the name; there is no type; deleted the "tax_US" frame */
285 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
286 : 0 : qof_book_option_frame_delete(book, "tax_US");
287 : : }
288 : : else
289 : : { /* need to delete the name and keep "tax_US" frame */
290 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
291 : : }
292 : : }
293 : : else
294 : : { /* change the name & keep "tax_US" frame */
295 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
296 : : }
297 : : }
298 : : }
299 : : else /* no name change */
300 : : {
301 : 0 : if (type_changed)
302 : : {
303 : 0 : auto book = gnc_get_current_book();
304 : 0 : if ((g_strcmp0 (tax_type, "Other") == 0) ||
305 : 0 : (g_strcmp0 (tax_type, "") == 0) ||
306 : : (tax_type == nullptr))
307 : : {
308 : 0 : if ((g_strcmp0 (tax_name, "") == 0) ||
309 : : (tax_name == nullptr))
310 : : {/* delete the type; there is no name; delete the "tax_US" frame */
311 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
312 : 0 : qof_book_option_frame_delete(book, "tax_US");
313 : : }
314 : : else
315 : : { /* need to delete the type and keep "tax_US" frame */
316 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
317 : : }
318 : : }
319 : : else
320 : : { /* change the type & keep "tax_US" frame */
321 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
322 : : }
323 : : } /*else no name and no type change - do nothing */
324 : : }
325 : 0 : }
326 : :
327 : : const char*
328 : 0 : gnc_get_current_book_tax_name (void)
329 : : {
330 : 0 : auto book = gnc_get_current_book();
331 : 0 : auto tax_name = qof_book_get_string_option(book, OPTION_TAXUS_NAME);
332 : 0 : if (tax_name)
333 : : {
334 : 0 : return tax_name;
335 : : }
336 : : else
337 : : {
338 : : const char* old_option_taxus_name =
339 : 0 : qof_book_get_string_option(book, OLD_OPTION_TAXUS_NAME);
340 : 0 : if (old_option_taxus_name)
341 : : {
342 : 0 : char* taxus_name = g_strdup(old_option_taxus_name);
343 : : const char* old_option_taxus_type =
344 : 0 : qof_book_get_string_option(book, OLD_OPTION_TAXUS_TYPE);
345 : 0 : if (old_option_taxus_type)
346 : : { /* switch both name and type and remove unused frames */
347 : 0 : char* taxus_type = g_strdup(old_option_taxus_type);
348 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
349 : 0 : qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
350 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
351 : 0 : qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
352 : 0 : qof_book_option_frame_delete(book, "book/tax_US");
353 : 0 : qof_book_option_frame_delete(book, "book");
354 : 0 : g_free (taxus_type);
355 : : }
356 : : else
357 : : { /* switch just name and remove unused frames */
358 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
359 : 0 : qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
360 : 0 : qof_book_option_frame_delete(book, "book/tax_US");
361 : 0 : qof_book_option_frame_delete(book, "book");
362 : : }
363 : 0 : g_free (taxus_name);
364 : 0 : return qof_book_get_string_option(book, OPTION_TAXUS_NAME);
365 : : }
366 : 0 : return nullptr;
367 : : }
368 : : }
369 : :
370 : : const char*
371 : 0 : gnc_get_current_book_tax_type (void)
372 : : {
373 : 0 : auto book = gnc_get_current_book();
374 : : auto tax_type =
375 : 0 : qof_book_get_string_option(book, OPTION_TAXUS_TYPE);
376 : 0 : if (tax_type)
377 : : {
378 : 0 : return tax_type;
379 : : }
380 : : else
381 : : {
382 : : auto old_option_taxus_type =
383 : 0 : qof_book_get_string_option(book, OLD_OPTION_TAXUS_TYPE);
384 : 0 : if (old_option_taxus_type)
385 : : {
386 : 0 : auto taxus_type = g_strdup(old_option_taxus_type);
387 : : auto old_option_taxus_name =
388 : 0 : qof_book_get_string_option(book, OLD_OPTION_TAXUS_NAME);
389 : 0 : if (old_option_taxus_name)
390 : : { /* switch both name and type and remove unused frames */
391 : 0 : auto taxus_name = g_strdup(old_option_taxus_name);
392 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
393 : 0 : qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
394 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
395 : 0 : qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
396 : 0 : qof_book_option_frame_delete(book, "book/tax_US");
397 : 0 : qof_book_option_frame_delete(book, "book");
398 : 0 : g_free (taxus_name);
399 : : }
400 : : else
401 : : { /* switch just type and remove unused frames */
402 : 0 : qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
403 : 0 : qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
404 : 0 : qof_book_option_frame_delete(book, "book/tax_US");
405 : 0 : qof_book_option_frame_delete(book, "book");
406 : : }
407 : 0 : g_free (taxus_type);
408 : 0 : return qof_book_get_string_option(book, OPTION_TAXUS_TYPE);
409 : : }
410 : 0 : return nullptr;
411 : : }
412 : : }
413 : :
414 : : Account *
415 : 1263 : gnc_get_current_root_account (void)
416 : : {
417 : 1263 : return gnc_book_get_root_account (gnc_get_current_book ());
418 : : }
419 : :
420 : : gnc_commodity_table *
421 : 1517 : gnc_get_current_commodities (void)
422 : : {
423 : 1517 : if (gnc_current_session_exist())
424 : 1477 : return gnc_commodity_table_get_table (gnc_get_current_book ());
425 : 40 : return nullptr;
426 : : }
427 : :
428 : : char*
429 : 0 : gnc_get_account_name_for_split_register(const Account *account,
430 : : gboolean show_leaf_accounts)
431 : : {
432 : 0 : if (show_leaf_accounts)
433 : 0 : return g_strdup (xaccAccountGetName (account));
434 : : else
435 : 0 : return gnc_account_get_full_name (account);
436 : : }
437 : :
438 : : char*
439 : 0 : gnc_get_account_name_for_register(const Account *account)
440 : : {
441 : : auto show_leaf_accounts =
442 : 0 : gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
443 : : GNC_PREF_SHOW_LEAF_ACCT_NAMES);
444 : :
445 : 0 : return gnc_get_account_name_for_split_register(account, show_leaf_accounts);
446 : : }
447 : :
448 : : Account *
449 : 0 : gnc_account_lookup_for_register(const Account *base_account, const char* name)
450 : : {
451 : : auto show_leaf_accounts =
452 : 0 : gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
453 : : GNC_PREF_SHOW_LEAF_ACCT_NAMES);
454 : :
455 : 0 : if (show_leaf_accounts)
456 : 0 : return gnc_account_lookup_by_name (base_account, name);
457 : : else
458 : 0 : return gnc_account_lookup_by_full_name (base_account, name);
459 : : }
460 : :
461 : : /********************************************************************\
462 : : * gnc_get_reconcile_str *
463 : : * return the i18n'd string for the given reconciled flag *
464 : : * *
465 : : * Args: reconciled_flag - the flag to convert into a string *
466 : : * Returns: the i18n'd reconciled string *
467 : : \********************************************************************/
468 : : const char*
469 : 0 : gnc_get_reconcile_str (char reconciled_flag)
470 : : {
471 : 0 : switch (reconciled_flag)
472 : : {
473 : 0 : case NREC:
474 : 0 : return C_("Reconciled flag 'not cleared'", "n");
475 : 0 : case CREC:
476 : 0 : return C_("Reconciled flag 'cleared'", "c");
477 : 0 : case YREC:
478 : 0 : return C_("Reconciled flag 'reconciled'", "y");
479 : 0 : case FREC:
480 : 0 : return C_("Reconciled flag 'frozen'", "f");
481 : 0 : case VREC:
482 : 0 : return C_("Reconciled flag 'void'", "v");
483 : 0 : default:
484 : 0 : PERR("Bad reconciled flag\n");
485 : 0 : return nullptr;
486 : : }
487 : : }
488 : :
489 : : /********************************************************************\
490 : : * gnc_get_reconcile_valid_flags *
491 : : * return a string containing the list of reconciled flags *
492 : : * *
493 : : * Returns: the i18n'd reconciled flags string *
494 : : \********************************************************************/
495 : : const char*
496 : 0 : gnc_get_reconcile_valid_flags (void)
497 : : {
498 : : static const char flags[] = { NREC, CREC, YREC, FREC, VREC, 0 };
499 : 0 : return flags;
500 : : }
501 : :
502 : : /********************************************************************\
503 : : * gnc_get_reconcile_flag_order *
504 : : * return a string containing the reconciled-flag change order *
505 : : * *
506 : : * Args: reconciled_flag - the flag to convert into a string *
507 : : * Returns: the i18n'd reconciled string *
508 : : \********************************************************************/
509 : : const char*
510 : 0 : gnc_get_reconcile_flag_order (void)
511 : : {
512 : : static const char flags[] = { NREC, CREC, 0 };
513 : 0 : return flags;
514 : : }
515 : :
516 : : const char*
517 : 0 : gnc_get_doclink_str (char link_flag)
518 : : {
519 : 0 : switch (link_flag)
520 : : {
521 : 0 : case WLINK:
522 : 0 : return C_("Document Link flag for 'web'", "w");
523 : 0 : case FLINK:
524 : 0 : return C_("Document Link flag for 'file'", "f");
525 : 0 : case ' ':
526 : 0 : return " ";
527 : 0 : default:
528 : 0 : PERR("Bad link flag");
529 : 0 : return nullptr;
530 : : }
531 : : }
532 : :
533 : : const char*
534 : 0 : gnc_get_doclink_valid_flags (void)
535 : : {
536 : : static const char flags[] = { FLINK, WLINK, ' ', 0 };
537 : 0 : return flags;
538 : : }
539 : :
540 : : const char*
541 : 0 : gnc_get_doclink_flag_order (void)
542 : : {
543 : : static const char flags[] = { FLINK, WLINK, ' ', 0 };
544 : 0 : return flags;
545 : : }
546 : :
547 : : static const char*
548 : 0 : equity_base_name (GNCEquityType equity_type)
549 : : {
550 : 0 : switch (equity_type)
551 : : {
552 : 0 : case EQUITY_OPENING_BALANCE:
553 : 0 : return N_("Opening Balances");
554 : :
555 : 0 : case EQUITY_RETAINED_EARNINGS:
556 : 0 : return N_("Retained Earnings");
557 : :
558 : 0 : default:
559 : 0 : return nullptr;
560 : : }
561 : : }
562 : :
563 : : Account *
564 : 0 : gnc_find_or_create_equity_account (Account *root,
565 : : GNCEquityType equity_type,
566 : : gnc_commodity *currency)
567 : : {
568 : 0 : g_return_val_if_fail (equity_type >= 0, nullptr);
569 : 0 : g_return_val_if_fail (equity_type < NUM_EQUITY_TYPES, nullptr);
570 : 0 : g_return_val_if_fail (currency != nullptr, nullptr);
571 : 0 : g_return_val_if_fail (root != nullptr, nullptr);
572 : 0 : g_return_val_if_fail (gnc_commodity_is_currency(currency), nullptr);
573 : :
574 : 0 : auto use_eq_op_feature = equity_type == EQUITY_OPENING_BALANCE && gnc_using_equity_type_opening_balance_account (gnc_get_current_book ());
575 : :
576 : 0 : if (use_eq_op_feature)
577 : : {
578 : 0 : auto account = gnc_account_lookup_by_opening_balance (root, currency);
579 : 0 : if (account)
580 : 0 : return account;
581 : : }
582 : :
583 : 0 : auto base_name = equity_base_name (equity_type);
584 : :
585 : 0 : auto account = gnc_account_lookup_by_name(root, base_name);
586 : 0 : if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
587 : 0 : account = nullptr;
588 : :
589 : 0 : if (!account)
590 : : {
591 : 0 : base_name = base_name && *base_name ? _(base_name) : "";
592 : :
593 : 0 : account = gnc_account_lookup_by_name(root, base_name);
594 : 0 : if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
595 : 0 : account = nullptr;
596 : : }
597 : :
598 : 0 : auto base_name_exists = (account != nullptr);
599 : :
600 : 0 : if (account &&
601 : 0 : gnc_commodity_equiv (currency, xaccAccountGetCommodity (account)))
602 : : {
603 : 0 : if (use_eq_op_feature)
604 : 0 : xaccAccountSetIsOpeningBalance (account, TRUE);
605 : 0 : return account;
606 : : }
607 : :
608 : 0 : auto name = g_strconcat (base_name, " - ",
609 : : gnc_commodity_get_mnemonic (currency), nullptr);
610 : 0 : account = gnc_account_lookup_by_name(root, name);
611 : 0 : if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
612 : 0 : account = nullptr;
613 : :
614 : 0 : auto name_exists = (account != nullptr);
615 : :
616 : 0 : if (account &&
617 : 0 : gnc_commodity_equiv (currency, xaccAccountGetCommodity (account)))
618 : : {
619 : 0 : if (use_eq_op_feature)
620 : 0 : xaccAccountSetIsOpeningBalance (account, TRUE);
621 : 0 : return account;
622 : : }
623 : :
624 : : /* Couldn't find one, so create it */
625 : 0 : if (name_exists && base_name_exists)
626 : : {
627 : 0 : PWARN ("equity account with unexpected currency");
628 : 0 : g_free (name);
629 : 0 : return nullptr;
630 : : }
631 : :
632 : 0 : if (!base_name_exists &&
633 : 0 : gnc_commodity_equiv (currency, gnc_default_currency ()))
634 : : {
635 : 0 : g_free (name);
636 : 0 : name = g_strdup (base_name);
637 : : }
638 : :
639 : 0 : auto parent = gnc_account_lookup_by_name(root, _("Equity"));
640 : 0 : if (!parent || xaccAccountGetType (parent) != ACCT_TYPE_EQUITY)
641 : 0 : parent = root;
642 : 0 : g_assert(parent);
643 : :
644 : 0 : account = xaccMallocAccount (gnc_account_get_book(root));
645 : :
646 : 0 : xaccAccountBeginEdit (account);
647 : :
648 : 0 : xaccAccountSetName (account, name);
649 : 0 : xaccAccountSetType (account, ACCT_TYPE_EQUITY);
650 : 0 : xaccAccountSetCommodity (account, currency);
651 : :
652 : 0 : if (use_eq_op_feature)
653 : 0 : xaccAccountSetIsOpeningBalance (account, TRUE);
654 : :
655 : 0 : xaccAccountBeginEdit (parent);
656 : 0 : gnc_account_append_child (parent, account);
657 : 0 : xaccAccountCommitEdit (parent);
658 : :
659 : 0 : xaccAccountCommitEdit (account);
660 : :
661 : 0 : g_free (name);
662 : :
663 : 0 : return account;
664 : : }
665 : :
666 : : gboolean
667 : 0 : gnc_account_create_opening_balance (Account *account,
668 : : gnc_numeric balance,
669 : : time64 date,
670 : : QofBook *book)
671 : : {
672 : 0 : if (gnc_numeric_zero_p (balance))
673 : 0 : return TRUE;
674 : :
675 : 0 : g_return_val_if_fail (account != nullptr, FALSE);
676 : 0 : auto commodity = xaccAccountGetCommodity (account);
677 : 0 : g_return_val_if_fail (gnc_commodity_is_currency (commodity), FALSE);
678 : :
679 : : auto equity_account =
680 : 0 : gnc_find_or_create_equity_account (gnc_account_get_root(account),
681 : : EQUITY_OPENING_BALANCE,
682 : : commodity);
683 : 0 : if (!equity_account)
684 : 0 : return FALSE;
685 : :
686 : 0 : xaccAccountBeginEdit (account);
687 : 0 : xaccAccountBeginEdit (equity_account);
688 : :
689 : 0 : auto trans = xaccMallocTransaction (book);
690 : :
691 : 0 : xaccTransBeginEdit (trans);
692 : :
693 : 0 : xaccTransSetCurrency (trans, gnc_account_or_default_currency (account, nullptr));
694 : 0 : xaccTransSetDatePostedSecsNormalized (trans, date);
695 : 0 : xaccTransSetDescription (trans, _("Opening Balance"));
696 : :
697 : 0 : auto split = xaccMallocSplit (book);
698 : :
699 : 0 : xaccTransAppendSplit (trans, split);
700 : 0 : xaccAccountInsertSplit (account, split);
701 : :
702 : 0 : xaccSplitSetAmount (split, balance);
703 : 0 : xaccSplitSetValue (split, balance);
704 : :
705 : 0 : balance = gnc_numeric_neg (balance);
706 : :
707 : 0 : split = xaccMallocSplit (book);
708 : :
709 : 0 : xaccTransAppendSplit (trans, split);
710 : 0 : xaccAccountInsertSplit (equity_account, split);
711 : :
712 : 0 : xaccSplitSetAmount (split, balance);
713 : 0 : xaccSplitSetValue (split, balance);
714 : :
715 : 0 : xaccTransCommitEdit (trans);
716 : 0 : xaccAccountCommitEdit (equity_account);
717 : 0 : xaccAccountCommitEdit (account);
718 : :
719 : 0 : return TRUE;
720 : : }
721 : :
722 : :
723 : : gnc_commodity *
724 : 1497 : gnc_locale_default_currency_nodefault (void)
725 : : {
726 : 1497 : auto table = gnc_get_current_commodities ();
727 : 1497 : auto code = gnc_locale_default_iso_currency_code ();
728 : :
729 : 1497 : auto currency = gnc_commodity_table_lookup (table,
730 : : GNC_COMMODITY_NS_CURRENCY,
731 : : code);
732 : :
733 : 1497 : return (currency ? currency : nullptr);
734 : : }
735 : :
736 : : gnc_commodity*
737 : 1497 : gnc_locale_default_currency (void)
738 : : {
739 : 1497 : auto currency = gnc_locale_default_currency_nodefault ();
740 : :
741 : 1497 : return (currency ? currency :
742 : 20 : gnc_commodity_table_lookup (gnc_get_current_commodities (),
743 : 1497 : GNC_COMMODITY_NS_CURRENCY, "USD"));
744 : : }
745 : :
746 : :
747 : : static gnc_commodity*
748 : 1497 : gnc_default_currency_common (char* requested_currency,
749 : : const char* section)
750 : : {
751 : 1497 : gnc_commodity *currency = nullptr;
752 : :
753 : 1497 : if (requested_currency)
754 : 0 : return gnc_commodity_table_lookup(gnc_get_current_commodities(),
755 : : GNC_COMMODITY_NS_CURRENCY,
756 : 0 : requested_currency);
757 : :
758 : 2974 : if (gnc_current_session_exist() &&
759 : 1477 : gnc_prefs_get_bool (section, GNC_PREF_CURRENCY_CHOICE_OTHER))
760 : : {
761 : 0 : auto mnemonic = gnc_prefs_get_string(section, GNC_PREF_CURRENCY_OTHER);
762 : 0 : currency = gnc_commodity_table_lookup(gnc_get_current_commodities(),
763 : : GNC_COMMODITY_NS_CURRENCY,
764 : : mnemonic);
765 : 0 : DEBUG("mnemonic %s, result %p",
766 : : mnemonic && *mnemonic ? mnemonic : "(null)", currency);
767 : 0 : g_free(mnemonic);
768 : : }
769 : :
770 : 1497 : if (!currency)
771 : 1497 : currency = gnc_locale_default_currency ();
772 : :
773 : 1497 : if (currency)
774 : : {
775 : 1477 : g_free (requested_currency);
776 : : }
777 : :
778 : 1497 : return currency;
779 : : }
780 : :
781 : : gnc_commodity*
782 : 536 : gnc_default_currency (void)
783 : : {
784 : 536 : return gnc_default_currency_common (user_default_currency,
785 : 536 : GNC_PREFS_GROUP_GENERAL);
786 : : }
787 : :
788 : : gnc_commodity*
789 : 0 : gnc_account_or_default_currency(const Account* account,
790 : : gboolean * currency_from_account_found)
791 : : {
792 : : gnc_commodity *currency;
793 : 0 : if (!account)
794 : : {
795 : 0 : if (currency_from_account_found)
796 : 0 : *currency_from_account_found = FALSE;
797 : 0 : return gnc_default_currency();
798 : : }
799 : :
800 : 0 : currency = gnc_account_get_currency_or_parent(account);
801 : 0 : if (currency)
802 : : {
803 : 0 : if (currency_from_account_found)
804 : 0 : *currency_from_account_found = TRUE;
805 : : }
806 : : else
807 : : {
808 : 0 : if (currency_from_account_found)
809 : 0 : *currency_from_account_found = FALSE;
810 : 0 : currency = gnc_default_currency();
811 : : }
812 : 0 : return currency;
813 : : }
814 : :
815 : :
816 : :
817 : : gnc_commodity*
818 : 961 : gnc_default_report_currency (void)
819 : : {
820 : 961 : return gnc_default_currency_common (user_report_currency,
821 : 961 : GNC_PREFS_GROUP_GENERAL_REPORT);
822 : : }
823 : :
824 : : static void
825 : 0 : gnc_currency_changed_cb (GSettings *settings, char* key, gpointer user_data)
826 : : {
827 : 0 : user_default_currency = nullptr;
828 : 0 : user_report_currency = nullptr;
829 : 0 : gnc_hook_run(HOOK_CURRENCY_CHANGED, nullptr);
830 : 0 : }
831 : :
832 : :
833 : : GNCPrintAmountInfo
834 : 530 : gnc_default_print_info (gboolean use_symbol)
835 : : {
836 : : static GNCPrintAmountInfo info;
837 : : static bool got_it = false;
838 : :
839 : : /* These must be updated each time. */
840 : 530 : info.use_symbol = use_symbol ? 1 : 0;
841 : 530 : info.commodity = gnc_default_currency ();
842 : :
843 : 530 : if (got_it)
844 : 526 : return info;
845 : :
846 : 4 : auto lc = gnc_localeconv ();
847 : :
848 : 4 : info.max_decimal_places = lc->frac_digits;
849 : 4 : info.min_decimal_places = lc->frac_digits;
850 : :
851 : 4 : info.use_separators = 1;
852 : 4 : info.use_locale = 1;
853 : 4 : info.monetary = 1;
854 : 4 : info.force_fit = 0;
855 : 4 : info.round = 0;
856 : :
857 : 4 : got_it = TRUE;
858 : :
859 : 4 : return info;
860 : : }
861 : :
862 : : static bool
863 : 7980 : is_decimal_fraction (int fraction, uint8_t *max_decimal_places_p)
864 : : {
865 : 7980 : uint8_t max_decimal_places = 0;
866 : :
867 : 7980 : if (fraction <= 0)
868 : 0 : return false;
869 : :
870 : 24107 : while (fraction != 1)
871 : : {
872 : 16127 : if (fraction % 10 != 0)
873 : 0 : return false;
874 : :
875 : 16127 : fraction = fraction / 10;
876 : 16127 : max_decimal_places += 1;
877 : : }
878 : :
879 : 7980 : if (max_decimal_places_p)
880 : 7980 : *max_decimal_places_p = max_decimal_places;
881 : :
882 : 7980 : return true;
883 : : }
884 : :
885 : : GNCPrintAmountInfo
886 : 8304 : gnc_commodity_print_info (const gnc_commodity *commodity,
887 : : gboolean use_symbol)
888 : : {
889 : : GNCPrintAmountInfo info;
890 : :
891 : 8304 : if (commodity == nullptr)
892 : 324 : return gnc_default_print_info (use_symbol);
893 : :
894 : 7980 : info.commodity = commodity;
895 : :
896 : 7980 : auto is_iso = gnc_commodity_is_iso (commodity);
897 : :
898 : 7980 : if (is_decimal_fraction (gnc_commodity_get_fraction (commodity),
899 : : &info.max_decimal_places))
900 : : {
901 : 7980 : if (is_iso)
902 : 7685 : info.min_decimal_places = info.max_decimal_places;
903 : : else
904 : 295 : info.min_decimal_places = 0;
905 : : }
906 : : else
907 : 0 : info.max_decimal_places = info.min_decimal_places = 0;
908 : :
909 : 7980 : info.use_separators = 1;
910 : 7980 : info.use_symbol = use_symbol ? 1 : 0;
911 : 7980 : info.use_locale = is_iso ? 1 : 0;
912 : 7980 : info.monetary = 1;
913 : 7980 : info.force_fit = 0;
914 : 7980 : info.round = 0;
915 : :
916 : 7980 : return info;
917 : : }
918 : :
919 : : static GNCPrintAmountInfo
920 : 0 : gnc_account_print_info_helper(const Account *account, gboolean use_symbol,
921 : : gnc_commodity * (*efffunc)(const Account *),
922 : : int (*scufunc)(const Account*))
923 : : {
924 : : GNCPrintAmountInfo info;
925 : :
926 : 0 : if (account == nullptr)
927 : 0 : return gnc_default_print_info (use_symbol);
928 : :
929 : 0 : info.commodity = efffunc (account);
930 : :
931 : 0 : auto is_iso = gnc_commodity_is_iso (info.commodity);
932 : :
933 : 0 : auto scu = scufunc (account);
934 : :
935 : 0 : if (is_decimal_fraction (scu, &info.max_decimal_places))
936 : : {
937 : 0 : if (is_iso)
938 : 0 : info.min_decimal_places = info.max_decimal_places;
939 : : else
940 : 0 : info.min_decimal_places = 0;
941 : : }
942 : : else
943 : 0 : info.max_decimal_places = info.min_decimal_places = 0;
944 : :
945 : 0 : info.use_separators = 1;
946 : 0 : info.use_symbol = use_symbol ? 1 : 0;
947 : 0 : info.use_locale = is_iso ? 1 : 0;
948 : 0 : info.monetary = 1;
949 : 0 : info.force_fit = 0;
950 : 0 : info.round = 0;
951 : :
952 : 0 : return info;
953 : : }
954 : :
955 : : GNCPrintAmountInfo
956 : 0 : gnc_account_print_info (const Account *account, gboolean use_symbol)
957 : : {
958 : 0 : return gnc_account_print_info_helper(account, use_symbol,
959 : : xaccAccountGetCommodity,
960 : 0 : xaccAccountGetCommoditySCU);
961 : : }
962 : :
963 : : GNCPrintAmountInfo
964 : 0 : gnc_split_amount_print_info (Split *split, gboolean use_symbol)
965 : : {
966 : 0 : if (!split)
967 : : {
968 : 0 : GNCPrintAmountInfo info = gnc_default_share_print_info ();
969 : 0 : info.use_symbol = use_symbol;
970 : 0 : return info;
971 : : }
972 : :
973 : 0 : return gnc_account_print_info (xaccSplitGetAccount (split), use_symbol);
974 : : }
975 : :
976 : : static GNCPrintAmountInfo
977 : 1 : gnc_default_print_info_helper (int decplaces)
978 : : {
979 : : GNCPrintAmountInfo info;
980 : :
981 : 1 : info.commodity = nullptr;
982 : :
983 : 1 : info.max_decimal_places = decplaces;
984 : 1 : info.min_decimal_places = 0;
985 : :
986 : 1 : info.use_separators = 1;
987 : 1 : info.use_symbol = 0;
988 : 1 : info.use_locale = 1;
989 : 1 : info.monetary = 1;
990 : 1 : info.force_fit = 0;
991 : 1 : info.round = 0;
992 : :
993 : 1 : return info;
994 : : }
995 : :
996 : : GNCPrintAmountInfo
997 : 7 : gnc_default_share_print_info (void)
998 : : {
999 : : static GNCPrintAmountInfo info;
1000 : : static bool got_it = false;
1001 : :
1002 : 7 : if (!got_it)
1003 : : {
1004 : 1 : info = gnc_default_print_info_helper (5);
1005 : 1 : info.monetary = 0;
1006 : 1 : got_it = TRUE;
1007 : : }
1008 : :
1009 : 7 : return info;
1010 : : }
1011 : :
1012 : : GNCPrintAmountInfo
1013 : 7 : gnc_share_print_info_places (int decplaces)
1014 : : {
1015 : : GNCPrintAmountInfo info;
1016 : :
1017 : 7 : info = gnc_default_share_print_info ();
1018 : 7 : info.max_decimal_places = decplaces;
1019 : 7 : info.min_decimal_places = decplaces;
1020 : 7 : info.force_fit = 1;
1021 : 7 : info.round = 1;
1022 : 7 : return info;
1023 : : }
1024 : :
1025 : : GNCPrintAmountInfo
1026 : 221 : gnc_price_print_info (const gnc_commodity *curr, gboolean use_symbol)
1027 : : {
1028 : : GNCPrintAmountInfo info;
1029 : 221 : auto force = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL,
1030 : : GNC_PREF_PRICES_FORCE_DECIMAL);
1031 : 221 : info.commodity = curr;
1032 : :
1033 : 221 : if (info.commodity)
1034 : : {
1035 : 221 : int frac = gnc_commodity_get_fraction (curr);
1036 : 221 : guint8 decplaces = 0;
1037 : 663 : while (frac != 1 && (frac % 10) == 0 && (frac /= 10)) ++decplaces;
1038 : 221 : if (force)
1039 : 0 : decplaces += 2;
1040 : 221 : info.max_decimal_places = decplaces;
1041 : 221 : info.min_decimal_places = decplaces;
1042 : : }
1043 : : else
1044 : : {
1045 : 0 : info.max_decimal_places = 6;
1046 : 0 : info.min_decimal_places = 0;
1047 : : }
1048 : :
1049 : 221 : info.use_separators = 1;
1050 : 221 : info.use_symbol = use_symbol ? 1 : 0;
1051 : 221 : info.use_locale = 1;
1052 : 221 : info.monetary = 1;
1053 : :
1054 : 221 : info.force_fit = force;
1055 : 221 : info.round = force;
1056 : 221 : return info;
1057 : : }
1058 : :
1059 : : GNCPrintAmountInfo
1060 : 0 : gnc_default_price_print_info (const gnc_commodity *curr)
1061 : : {
1062 : 0 : return gnc_price_print_info (curr, FALSE);
1063 : : }
1064 : :
1065 : :
1066 : : GNCPrintAmountInfo
1067 : 0 : gnc_integral_print_info (void)
1068 : : {
1069 : : static GNCPrintAmountInfo info;
1070 : : static bool got_it = false;
1071 : :
1072 : 0 : if (!got_it)
1073 : : {
1074 : 0 : info = gnc_default_print_info_helper (0);
1075 : 0 : got_it = TRUE;
1076 : : }
1077 : :
1078 : 0 : return info;
1079 : : }
1080 : :
1081 : : /* Utility function for printing non-negative amounts */
1082 : : static int
1083 : 11675 : PrintAmountInternal(char* buf, gnc_numeric val, const GNCPrintAmountInfo *info)
1084 : : {
1085 : 11675 : auto *lc = gnc_localeconv();
1086 : 11675 : constexpr size_t buf_size = 128;
1087 : : char temp_buf[buf_size];
1088 : :
1089 : 11675 : g_return_val_if_fail (info != nullptr, 0);
1090 : :
1091 : 11675 : if (gnc_numeric_check (val))
1092 : : {
1093 : 0 : PWARN ("Bad numeric: %s.",
1094 : : gnc_numeric_errorCode_to_string(gnc_numeric_check (val)));
1095 : 0 : *buf = '\0';
1096 : 0 : return 0;
1097 : : }
1098 : :
1099 : : /* Print the absolute value, but remember sign */
1100 : 11675 : auto value_is_negative = gnc_numeric_negative_p (val);
1101 : 11675 : val = gnc_numeric_abs (val);
1102 : :
1103 : : /* Try to print as decimal. */
1104 : 11675 : auto value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
1105 : 11675 : if (!value_is_decimal && info->force_fit && info->round)
1106 : : {
1107 : : /* if there's a commodity use 100x the commodity's fraction. N.B. This
1108 : : * assumes that commodity fractions are multiples of 10, a reasonable
1109 : : * assumption in 2018. Otherwise, if there's a reasonable
1110 : : * max_decimal_places, use that.
1111 : : */
1112 : 0 : const int64_t denom = info->commodity ?
1113 : 0 : gnc_commodity_get_fraction(info->commodity) * 100 :
1114 : 0 : (info->max_decimal_places &&
1115 : 0 : info->max_decimal_places <= maximum_decimals) ?
1116 : 0 : pow_10[info->max_decimal_places] : pow_10[maximum_decimals];
1117 : 0 : val = gnc_numeric_convert(val, denom, GNC_HOW_RND_ROUND_HALF_UP);
1118 : 0 : value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
1119 : : }
1120 : 11675 : auto min_dp = info->min_decimal_places;
1121 : 11675 : auto max_dp = info->max_decimal_places;
1122 : :
1123 : : /* Don to limit the number of decimal places _UNLESS_ force_fit is
1124 : : * true. */
1125 : 11675 : if (!info->force_fit)
1126 : 10644 : max_dp = 99;
1127 : :
1128 : : /* rounding? -- can only ROUND if force_fit is also true */
1129 : 11675 : if (value_is_decimal && info->round && info->force_fit)
1130 : : {
1131 : : /* Limit the denom to 10^13 ~= 2^44, leaving max at ~524288 */
1132 : 519 : gnc_numeric rounding{5, pow_10[max_dp + 1]};
1133 : :
1134 : 519 : val = gnc_numeric_add(val, rounding, val.denom,
1135 : : GNC_HOW_DENOM_EXACT | GNC_HOW_RND_TRUNC);
1136 : :
1137 : 519 : if (gnc_numeric_check(val))
1138 : : {
1139 : 0 : PWARN ("Bad numeric from rounding: %s.",
1140 : : gnc_numeric_errorCode_to_string(gnc_numeric_check (val)));
1141 : 0 : *buf = '\0';
1142 : 0 : return 0;
1143 : : }
1144 : : }
1145 : :
1146 : : /* calculate the integer part and the remainder */
1147 : 11675 : auto whole = gnc_numeric_convert(val, 1, GNC_HOW_RND_TRUNC);
1148 : 11675 : val = gnc_numeric_sub (val, whole, GNC_DENOM_AUTO, GNC_HOW_RND_NEVER);
1149 : 11675 : if (gnc_numeric_check (val))
1150 : : {
1151 : 0 : PWARN ("Problem with remainder: %s.",
1152 : : gnc_numeric_errorCode_to_string(gnc_numeric_check (val)));
1153 : 0 : *buf = '\0';
1154 : 0 : return 0;
1155 : : }
1156 : :
1157 : : // Value may now be decimal, for example if the factional part is zero
1158 : 11675 : value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
1159 : : /* print the integer part without separators */
1160 : 11675 : snprintf(temp_buf, buf_size, "%" G_GINT64_FORMAT, whole.num);
1161 : 11675 : auto num_whole_digits = strlen (temp_buf);
1162 : :
1163 : 11675 : if (!info->use_separators)
1164 : 2048 : strcpy (buf, temp_buf);
1165 : : else
1166 : : {
1167 : : char* separator;
1168 : : char* group;
1169 : :
1170 : 9627 : if (info->monetary)
1171 : : {
1172 : 9108 : separator = lc->mon_thousands_sep;
1173 : 9108 : group = lc->mon_grouping;
1174 : : }
1175 : : else
1176 : : {
1177 : 519 : separator = lc->thousands_sep;
1178 : 519 : group = lc->grouping;
1179 : : }
1180 : :
1181 : 9627 : auto buf_ptr = buf;
1182 : 9627 : auto temp_ptr = &temp_buf[num_whole_digits - 1];
1183 : 9627 : auto group_count = 0;
1184 : :
1185 : 28644 : while (temp_ptr != temp_buf)
1186 : : {
1187 : 19017 : *buf_ptr++ = *temp_ptr--;
1188 : :
1189 : 19017 : if (*group != CHAR_MAX)
1190 : : {
1191 : 19017 : group_count++;
1192 : :
1193 : 19017 : if (group_count == *group)
1194 : : {
1195 : 3417 : g_utf8_strncpy(buf_ptr, separator, 1);
1196 : 3417 : buf_ptr = g_utf8_find_next_char(buf_ptr, nullptr);
1197 : 3417 : group_count = 0;
1198 : :
1199 : : /* Peek ahead at the next group code */
1200 : 3417 : switch (group[1])
1201 : : {
1202 : : /* A null char means repeat the last group indefinitely */
1203 : 3417 : case '\0':
1204 : 3417 : break;
1205 : : /* CHAR_MAX means no more grouping allowed */
1206 : 0 : case CHAR_MAX:
1207 : : /* fall through */
1208 : : /* Anything else means another group size */
1209 : : default:
1210 : 0 : group++;
1211 : 0 : break;
1212 : : }
1213 : : }
1214 : : }
1215 : : }
1216 : :
1217 : : /* We built the string backwards, now reverse */
1218 : 9627 : *buf_ptr++ = *temp_ptr;
1219 : 9627 : *buf_ptr = '\0';
1220 : 9627 : auto rev_buf = g_utf8_strreverse(buf, -1);
1221 : 9627 : strcpy (buf, rev_buf);
1222 : 9627 : g_free(rev_buf);
1223 : : } /* endif */
1224 : :
1225 : : /* at this point, buf contains the whole part of the number */
1226 : :
1227 : : /* If it's not decimal, print the fraction as an expression. */
1228 : 11675 : if (!value_is_decimal)
1229 : : {
1230 : 42 : val = gnc_numeric_reduce (val);
1231 : :
1232 : 42 : if (val.denom > 0)
1233 : 42 : snprintf (temp_buf, buf_size, "%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
1234 : : val.num, val.denom);
1235 : : else
1236 : 0 : snprintf (temp_buf, buf_size, "%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT,
1237 : 0 : val.num, -val.denom);
1238 : :
1239 : 42 : if (whole.num == 0)
1240 : 0 : *buf = '\0';
1241 : 42 : else if (value_is_negative)
1242 : 0 : strcat(buf, " - ");
1243 : : else
1244 : 42 : strcat(buf, " + ");
1245 : :
1246 : 42 : strcat (buf, temp_buf);
1247 : : }
1248 : : else
1249 : : {
1250 : 11633 : guint8 num_decimal_places = 0;
1251 : 11633 : char* temp_ptr = temp_buf;
1252 : :
1253 : 23266 : auto decimal_point = info->monetary
1254 : 11633 : ? lc->mon_decimal_point
1255 : : : lc->decimal_point;
1256 : 11633 : g_utf8_strncpy(temp_ptr, decimal_point, 1);
1257 : 11633 : temp_ptr = g_utf8_find_next_char(temp_ptr, nullptr);
1258 : :
1259 : 24434 : while (!gnc_numeric_zero_p (val)
1260 : 12801 : && (val.denom != 1)
1261 : 37235 : && (num_decimal_places < max_dp))
1262 : : {
1263 : : gint64 digit;
1264 : :
1265 : 12801 : val.denom = val.denom / 10;
1266 : :
1267 : 12801 : digit = val.num / val.denom;
1268 : :
1269 : 12801 : *temp_ptr++ = digit + '0';
1270 : 12801 : num_decimal_places++;
1271 : :
1272 : 12801 : val.num = val.num - (digit * val.denom);
1273 : : }
1274 : :
1275 : 26729 : while (num_decimal_places < min_dp)
1276 : : {
1277 : 15096 : *temp_ptr++ = '0';
1278 : 15096 : num_decimal_places++;
1279 : : }
1280 : :
1281 : : /* cap the end and move to the last character */
1282 : 11633 : *temp_ptr-- = '\0';
1283 : :
1284 : : /* Here we strip off trailing decimal zeros per the argument. */
1285 : 11633 : while (*temp_ptr == '0' && num_decimal_places > min_dp)
1286 : : {
1287 : 0 : *temp_ptr-- = '\0';
1288 : 0 : num_decimal_places--;
1289 : : }
1290 : :
1291 : 11633 : if (num_decimal_places > max_dp)
1292 : : {
1293 : 0 : PWARN ("max_decimal_places too small; limit %d, value %s%s",
1294 : : info->max_decimal_places, buf, temp_buf);
1295 : : }
1296 : :
1297 : 11633 : strcat (buf, temp_buf);
1298 : : }
1299 : :
1300 : 11675 : return strlen(buf);
1301 : : }
1302 : :
1303 : : /**
1304 : : * @param bufp Should be at least 64 chars.
1305 : : **/
1306 : : int
1307 : 11675 : xaccSPrintAmount (char* bufp, gnc_numeric val, GNCPrintAmountInfo info)
1308 : : {
1309 : 11675 : auto orig_bufp = bufp;
1310 : 11675 : auto currency_symbol = "";
1311 : : const char* sign;
1312 : :
1313 : : char cs_precedes;
1314 : : char sep_by_space;
1315 : : char sign_posn;
1316 : :
1317 : 11675 : bool print_sign = true;
1318 : 11675 : bool print_absolute = false;
1319 : :
1320 : 11675 : if (!bufp)
1321 : 0 : return 0;
1322 : :
1323 : 11675 : auto lc = gnc_localeconv();
1324 : 11675 : if (info.use_locale)
1325 : 11425 : if (gnc_numeric_negative_p (val))
1326 : : {
1327 : 1096 : cs_precedes = lc->n_cs_precedes;
1328 : 1096 : sep_by_space = lc->n_sep_by_space;
1329 : : }
1330 : : else
1331 : : {
1332 : 10329 : cs_precedes = lc->p_cs_precedes;
1333 : 10329 : sep_by_space = lc->p_sep_by_space;
1334 : : }
1335 : : else
1336 : : {
1337 : 250 : cs_precedes = TRUE;
1338 : 250 : sep_by_space = TRUE;
1339 : : }
1340 : :
1341 : 11675 : if (info.commodity && info.use_symbol)
1342 : : {
1343 : 8388 : currency_symbol = gnc_commodity_get_nice_symbol (info.commodity);
1344 : 8388 : if (!gnc_commodity_is_iso (info.commodity))
1345 : : {
1346 : 248 : cs_precedes = FALSE;
1347 : 248 : sep_by_space = TRUE;
1348 : : }
1349 : : }
1350 : :
1351 : 11675 : if (gnc_numeric_negative_p (val))
1352 : : {
1353 : 1128 : sign = lc->negative_sign;
1354 : 1128 : sign_posn = lc->n_sign_posn;
1355 : : }
1356 : : else
1357 : : {
1358 : 10547 : sign = lc->positive_sign;
1359 : 10547 : sign_posn = lc->p_sign_posn;
1360 : : }
1361 : :
1362 : 11675 : if (gnc_numeric_zero_p (val) || (sign == nullptr) || (sign[0] == 0))
1363 : 10547 : print_sign = FALSE;
1364 : :
1365 : : /* See if we print sign now */
1366 : 11675 : if (print_sign && (sign_posn == 1))
1367 : 1128 : bufp = g_stpcpy(bufp, sign);
1368 : :
1369 : : /* Now see if we print currency */
1370 : 11675 : if (cs_precedes)
1371 : : {
1372 : : /* See if we print sign now */
1373 : 11427 : if (print_sign && (sign_posn == 3))
1374 : 0 : bufp = g_stpcpy(bufp, sign);
1375 : :
1376 : 11427 : if (info.use_symbol)
1377 : : {
1378 : 8140 : bufp = g_stpcpy(bufp, currency_symbol);
1379 : 8140 : if (sep_by_space)
1380 : 0 : bufp = g_stpcpy(bufp, " ");
1381 : : }
1382 : :
1383 : : /* See if we print sign now */
1384 : 11427 : if (print_sign && (sign_posn == 4))
1385 : 0 : bufp = g_stpcpy(bufp, sign);
1386 : : }
1387 : :
1388 : : /* Now see if we print parentheses */
1389 : 11675 : if (print_sign && (sign_posn == 0))
1390 : : {
1391 : 0 : bufp = g_stpcpy(bufp, "(");
1392 : 0 : print_absolute = TRUE;
1393 : : }
1394 : :
1395 : : /* Now print the value */
1396 : 11675 : bufp += PrintAmountInternal(bufp,
1397 : 0 : print_absolute ? gnc_numeric_abs(val) : val,
1398 : : &info);
1399 : :
1400 : : /* Now see if we print parentheses */
1401 : 11675 : if (print_sign && (sign_posn == 0))
1402 : 0 : bufp = g_stpcpy(bufp, ")");
1403 : :
1404 : : /* Now see if we print currency */
1405 : 11675 : if (!cs_precedes)
1406 : : {
1407 : : /* See if we print sign now */
1408 : 248 : if (print_sign && (sign_posn == 3))
1409 : 0 : bufp = g_stpcpy(bufp, sign);
1410 : :
1411 : 248 : if (info.use_symbol)
1412 : : {
1413 : 248 : if (sep_by_space)
1414 : 248 : bufp = g_stpcpy(bufp, " ");
1415 : 248 : bufp = g_stpcpy(bufp, currency_symbol);
1416 : : }
1417 : :
1418 : : /* See if we print sign now */
1419 : 248 : if (print_sign && (sign_posn == 4))
1420 : 0 : bufp = g_stpcpy(bufp, sign);
1421 : : }
1422 : :
1423 : : /* See if we print sign now */
1424 : 11675 : if (print_sign && (sign_posn == 2))
1425 : 0 : bufp = g_stpcpy(bufp, sign);
1426 : :
1427 : : /* return length of printed string */
1428 : 11675 : return (bufp - orig_bufp);
1429 : : }
1430 : :
1431 : : #define BUFLEN 1024
1432 : :
1433 : : const char*
1434 : 11673 : xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info)
1435 : : {
1436 : : /* hack alert -- this is not thread safe ... */
1437 : : static char buf[BUFLEN];
1438 : :
1439 : 11673 : if (!xaccSPrintAmount (buf, val, info))
1440 : 0 : buf[0] = '\0';
1441 : :
1442 : : /* its OK to return buf, since we declared it static */
1443 : 11673 : return buf;
1444 : : }
1445 : :
1446 : : const char*
1447 : 0 : gnc_print_amount_with_bidi_ltr_isolate (gnc_numeric val, GNCPrintAmountInfo info)
1448 : : {
1449 : : /* hack alert -- this is not thread safe ... */
1450 : : static char buf[BUFLEN];
1451 : : static const char ltr_isolate[] = { '\xe2', '\x81', '\xa6' };
1452 : : static const char ltr_pop_isolate[] = { '\xe2', '\x81', '\xa9' };
1453 : 0 : auto offset = info.use_symbol ? 3 : 0;
1454 : :
1455 : 0 : if (!gnc_commodity_is_currency (info.commodity))
1456 : 0 : offset = 0;
1457 : :
1458 : 0 : memset (buf, 0, BUFLEN);
1459 : 0 : if (!xaccSPrintAmount (buf + offset, val, info))
1460 : : {
1461 : 0 : buf[0] = '\0';
1462 : 0 : return buf;
1463 : : };
1464 : :
1465 : 0 : if (offset == 0)
1466 : 0 : return buf;
1467 : :
1468 : 0 : memcpy (buf, ltr_isolate, 3);
1469 : :
1470 : 0 : if (buf[BUFLEN - 4] == '\0')
1471 : : {
1472 : 0 : auto length = strlen (buf);
1473 : 0 : memcpy (buf + length, ltr_pop_isolate, 3);
1474 : : }
1475 : : else
1476 : : {
1477 : 0 : buf[BUFLEN - 1] = '\0';
1478 : 0 : memcpy (buf + BUFLEN - 4, ltr_pop_isolate, 3);
1479 : :
1480 : 0 : PWARN("buffer length %d exceeded, string truncated was %s", BUFLEN, buf);
1481 : : }
1482 : : /* its OK to return buf, since we declared it static
1483 : : and is immediately g_strdup'd */
1484 : 0 : return buf;
1485 : : }
1486 : :
1487 : : char*
1488 : 0 : gnc_wrap_text_with_bidi_ltr_isolate (const char* text)
1489 : : {
1490 : : static const char* ltr = "\u2066"; // ltr isolate
1491 : : static const char* pop = "\u2069"; // pop directional formatting
1492 : :
1493 : 0 : if (!text)
1494 : 0 : return nullptr;
1495 : :
1496 : 0 : return g_strconcat (ltr, text, pop, nullptr);
1497 : : }
1498 : :
1499 : : /********************************************************************\
1500 : : ********************************************************************/
1501 : :
1502 : : #define FUDGE .00001
1503 : :
1504 : : /* This function is basically untranslatable. I'd
1505 : : guess out of the 29 translations we have, 20 will have their number
1506 : : wordings in a totally different way than English has (not to
1507 : : mention gender-dependent number endings). Which means this
1508 : : word-by-word translation will be useless or even plain
1509 : : wrong. For this reason, we don't even start to pretend a
1510 : : word-by-word translation would be of any use, so we don't mark any
1511 : : of these strings for translation. cstim, 2007-04-15. */
1512 : : static const char* small_numbers[] =
1513 : : {
1514 : : /* Translators: This section is for generating the "amount, in
1515 : : words" field when printing a check. This function gets the
1516 : : wording right for English, but unfortunately not for most other
1517 : : languages. Decide for yourself whether the check printing is
1518 : : actually needed in your language; if not, you can safely skip the
1519 : : translation of all of these strings. */
1520 : : "Zero", "One", "Two", "Three", "Four",
1521 : : "Five", "Six", "Seven", "Eight", "Nine",
1522 : : "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
1523 : : "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen",
1524 : : "Twenty"
1525 : : };
1526 : : static const char* medium_numbers[] =
1527 : : {
1528 : : "Zero", "Ten", "Twenty", "Thirty", "Forty",
1529 : : "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
1530 : : };
1531 : : static const char* big_numbers[] =
1532 : : {
1533 : : /* Translators: This is the word for the number 10^2 */
1534 : : "Hundred",
1535 : : /* Translators: This is the word for the number 10^3 */
1536 : : "Thousand",
1537 : : /* Translators: This is the word for the number 10^6, one thousand
1538 : : thousands. */
1539 : : "Million",
1540 : : /* Translators: This is the word for the number 10^9, one thousand
1541 : : millions. WATCH OUT: In British English and many other languages
1542 : : this word is used for 10^12 which is one million millions! In
1543 : : contrast to this, here in GnuCash this is used in the American
1544 : : English meaning of 10^9. */
1545 : : "Billion",
1546 : : /* Translators: This is the word for the number 10^12, one million
1547 : : millions. */
1548 : : "Trillion",
1549 : : /* Translators: This is the word for the number 10^15 */
1550 : : "Quadrillion",
1551 : : /* Translators: This is the word for the number 10^18 */
1552 : : "Quintillion"
1553 : : };
1554 : :
1555 : : static char*
1556 : 0 : integer_to_words(gint64 val)
1557 : : {
1558 : 0 : if (val == 0)
1559 : 0 : return g_strdup("zero");
1560 : 0 : if (val < 0)
1561 : 0 : val = -val;
1562 : :
1563 : 0 : auto result = g_string_sized_new(100);
1564 : :
1565 : 0 : while (val >= 1000)
1566 : : {
1567 : 0 : int log_val = log10(val) / 3 + FUDGE;
1568 : 0 : int pow_val = exp(log_val * 3 * G_LN10) + FUDGE;
1569 : 0 : int this_part = val / pow_val;
1570 : 0 : val -= this_part * pow_val;
1571 : 0 : auto tmp = integer_to_words(this_part);
1572 : 0 : g_string_append_printf(result, "%s %s ", tmp, gettext(big_numbers[log_val]));
1573 : 0 : g_free(tmp);
1574 : : }
1575 : :
1576 : 0 : if (val >= 100)
1577 : : {
1578 : 0 : int this_part = val / 100;
1579 : 0 : val -= this_part * 100;
1580 : 0 : g_string_append_printf(result, "%s %s ",
1581 : : gettext(small_numbers[this_part]),
1582 : : gettext(big_numbers[0]));
1583 : : }
1584 : :
1585 : 0 : if (val > 20)
1586 : : {
1587 : 0 : int this_part = val / 10;
1588 : 0 : val -= this_part * 10;
1589 : 0 : g_string_append(result, gettext(medium_numbers[this_part]));
1590 : : g_string_append_c(result, ' ');
1591 : : }
1592 : :
1593 : 0 : if (val > 0)
1594 : : {
1595 : 0 : int this_part = val;
1596 : 0 : g_string_append(result, gettext(small_numbers[this_part]));
1597 : : g_string_append_c(result, ' ');
1598 : : }
1599 : :
1600 : 0 : result = g_string_truncate(result, result->len - 1);
1601 : 0 : return g_string_free(result, FALSE);
1602 : : }
1603 : :
1604 : : #ifdef _MSC_VER
1605 : : static double round(double x)
1606 : : {
1607 : : // A simple round() implementation because MSVC doesn't seem to have that
1608 : : return floor(x + 0.5);
1609 : : }
1610 : : #endif
1611 : :
1612 : : char*
1613 : 0 : number_to_words(double val, int64_t denom)
1614 : : {
1615 : 0 : if (val < 0) val = -val;
1616 : 0 : if (denom < 0) denom = -denom;
1617 : :
1618 : 0 : auto int_part = floor(val);
1619 : 0 : auto frac_part = static_cast<int64_t>(round((val - int_part) * denom));
1620 : :
1621 : 0 : auto int_string = integer_to_words(int_part);
1622 : : /* Inside of the gettext macro _(...) we must not use any macros but
1623 : : only plain string literals. For this reason, convert the strings
1624 : : separately. */
1625 : 0 : auto nomin_string = g_strdup_printf("%02" PRId64, frac_part);
1626 : 0 : auto denom_string = g_strdup_printf("%" PRId64, denom);
1627 : : auto full_string =
1628 : : /* Translators: This is for the "amount, in words" field in check
1629 : : printing. The first %s is the integer amount of dollars (or
1630 : : whatever currency), the second and third %s the cent amount as
1631 : : a fraction, e.g. 47/100. */
1632 : 0 : g_strdup_printf("%s and %s/%s",
1633 : : int_string, nomin_string, denom_string);
1634 : 0 : g_free(int_string);
1635 : 0 : g_free(nomin_string);
1636 : 0 : g_free(denom_string);
1637 : 0 : return full_string;
1638 : : }
1639 : :
1640 : : char*
1641 : 0 : numeric_to_words(gnc_numeric val)
1642 : : {
1643 : 0 : return number_to_words(gnc_numeric_to_double(val),
1644 : 0 : gnc_numeric_denom(val));
1645 : : }
1646 : :
1647 : : const char*
1648 : 0 : printable_value (double val, int denom)
1649 : : {
1650 : 0 : auto num = gnc_numeric_create(round(val * denom), denom);
1651 : 0 : auto info = gnc_share_print_info_places(log10(denom));
1652 : 0 : return xaccPrintAmount (num, info);
1653 : : }
1654 : :
1655 : : /********************************************************************\
1656 : : * xaccParseAmount *
1657 : : * parses amount strings using locale data *
1658 : : * *
1659 : : * Args: in_str -- pointer to string rep of num *
1660 : : * monetary -- boolean indicating whether value is monetary *
1661 : : * result -- pointer to result location, may be nullptr *
1662 : : * endstr -- used to store first digit not used in parsing *
1663 : : * Return: gboolean -- TRUE if a number found and parsed *
1664 : : * If FALSE, result is not changed *
1665 : : \********************************************************************/
1666 : :
1667 : : /* Parsing state machine states */
1668 : : enum ParseState
1669 : : {
1670 : : START_ST, /* Parsing initial whitespace */
1671 : : NEG_ST, /* Parsed a negative sign or a left paren */
1672 : : NUMER_ST, /* Parsing digits before grouping and decimal characters */
1673 : : FRAC_ST, /* Parsing the fractional portion of a number */
1674 : : DONE_ST, /* Finished, number is correct module grouping constraints */
1675 : : NO_NUM_ST /* Finished, number was malformed */
1676 : : };
1677 : :
1678 : : #define done_state(state) (((state) == DONE_ST) || ((state) == NO_NUM_ST))
1679 : :
1680 : : static inline int64_t
1681 : 3011 : multiplier (int num_decimals)
1682 : : {
1683 : 3011 : switch (num_decimals)
1684 : : {
1685 : 0 : case 12:
1686 : 0 : return 1000000000000;
1687 : 0 : case 11:
1688 : 0 : return 100000000000;
1689 : 0 : case 10:
1690 : 0 : return 10000000000;
1691 : 0 : case 9:
1692 : 0 : return 1000000000;
1693 : 192 : case 8:
1694 : 192 : return 100000000;
1695 : 276 : case 7:
1696 : 276 : return 10000000;
1697 : 294 : case 6:
1698 : 294 : return 1000000;
1699 : 258 : case 5:
1700 : 258 : return 100000;
1701 : 465 : case 4:
1702 : 465 : return 10000;
1703 : 369 : case 3:
1704 : 369 : return 1000;
1705 : 687 : case 2:
1706 : 687 : return 100;
1707 : 470 : case 1:
1708 : 470 : return 10;
1709 : 0 : case 0:
1710 : 0 : return 1;
1711 : 0 : default:
1712 : 0 : PERR("bad fraction length");
1713 : 0 : g_assert_not_reached();
1714 : : break;
1715 : : }
1716 : :
1717 : : return 1;
1718 : : }
1719 : :
1720 : : static gboolean
1721 : 3226 : xaccParseAmountInternal (const char* in_str, gboolean monetary,
1722 : : gunichar negative_sign, gunichar decimal_point,
1723 : : gunichar group_separator, const char* ignore_list,
1724 : : gboolean use_auto_decimal,
1725 : : gnc_numeric *result, char** endstr)
1726 : : {
1727 : : /* Initialize *endstr to in_str */
1728 : 3226 : if (endstr)
1729 : 140 : *endstr = (char* ) in_str;
1730 : :
1731 : 3226 : if (!in_str)
1732 : 0 : return FALSE;
1733 : :
1734 : : const char* in;
1735 : 3226 : if (!g_utf8_validate(in_str, -1, &in))
1736 : : {
1737 : 0 : printf("Invalid utf8 string '%s'. Bad character at position %ld.\n",
1738 : : in_str, g_utf8_pointer_to_offset (in_str, in));
1739 : 0 : return FALSE;
1740 : : }
1741 : :
1742 : : /* 'out_str' will be used to store digits for numeric conversion.
1743 : : * 'out' will be used to traverse out_str. */
1744 : 3226 : auto out_str = g_new(gchar, strlen(in_str) + 128);
1745 : 3226 : auto out = out_str;
1746 : :
1747 : : /* 'in' is used to traverse 'in_str'. */
1748 : 3226 : in = in_str;
1749 : :
1750 : 3226 : auto is_negative = false;
1751 : 3226 : auto got_decimal = false;
1752 : 3226 : auto need_paren = false;
1753 : 3226 : int64_t numer = 0;
1754 : 3226 : int64_t denom = 1;
1755 : :
1756 : : /* Initialize the state machine */
1757 : 3226 : auto state = START_ST;
1758 : :
1759 : : /* This while loop implements a state machine for parsing numbers. */
1760 : : while (TRUE)
1761 : : {
1762 : 41103 : auto next_state = state;
1763 : 41103 : auto uc = g_utf8_get_char(in);
1764 : :
1765 : : /* Ignore anything in the 'ignore list' */
1766 : 41103 : if (ignore_list && uc && g_utf8_strchr(ignore_list, -1, uc))
1767 : : {
1768 : 5 : in = g_utf8_next_char(in);
1769 : 5 : continue;
1770 : : }
1771 : :
1772 : : /* Note we never need to check for the end of 'in_str' explicitly.
1773 : : * The 'else' clauses on all the state transitions will handle that. */
1774 : 41098 : switch (state)
1775 : : {
1776 : : /* START_ST means we have parsed 0 or more whitespace characters */
1777 : 3232 : case START_ST:
1778 : 3232 : if (g_unichar_isdigit(uc))
1779 : : {
1780 : 3214 : int count = g_unichar_to_utf8(uc, out);
1781 : 3214 : out += count; /* we record the digits themselves in out_str
1782 : : * for later conversion by libc routines */
1783 : 3214 : next_state = NUMER_ST;
1784 : : }
1785 : 18 : else if (uc == decimal_point)
1786 : 0 : next_state = FRAC_ST;
1787 : 18 : else if (g_unichar_isspace(uc))
1788 : : ;
1789 : 12 : else if (uc == negative_sign)
1790 : : {
1791 : 6 : is_negative = TRUE;
1792 : 6 : next_state = NEG_ST;
1793 : : }
1794 : 6 : else if (uc == '(')
1795 : : {
1796 : 0 : is_negative = TRUE;
1797 : 0 : need_paren = TRUE;
1798 : 0 : next_state = NEG_ST;
1799 : : }
1800 : : else
1801 : 6 : next_state = NO_NUM_ST;
1802 : :
1803 : 3232 : break;
1804 : :
1805 : : /* NEG_ST means we have just parsed a negative sign. For now,
1806 : : * we only recognize formats where the negative sign comes first. */
1807 : 6 : case NEG_ST:
1808 : 6 : if (g_unichar_isdigit(uc))
1809 : : {
1810 : 6 : int count = g_unichar_to_utf8(uc, out);
1811 : 6 : out += count;
1812 : 6 : next_state = NUMER_ST;
1813 : : }
1814 : 0 : else if (uc == decimal_point)
1815 : 0 : next_state = FRAC_ST;
1816 : 0 : else if (g_unichar_isspace(uc))
1817 : : ;
1818 : : else
1819 : 0 : next_state = NO_NUM_ST;
1820 : :
1821 : 6 : break;
1822 : :
1823 : : /* NUMER_ST means we have started parsing the number, but
1824 : : * have not encountered a decimal separator. */
1825 : 23417 : case NUMER_ST:
1826 : 23417 : if (g_unichar_isdigit(uc))
1827 : : {
1828 : 18250 : int count = g_unichar_to_utf8(uc, out);
1829 : 18250 : out += count;
1830 : : }
1831 : 5167 : else if (uc == decimal_point)
1832 : 3108 : next_state = FRAC_ST;
1833 : 2059 : else if (uc == group_separator)
1834 : : ; //ignore it
1835 : 112 : else if (uc == ')' && need_paren)
1836 : : {
1837 : 0 : next_state = DONE_ST;
1838 : 0 : need_paren = FALSE;
1839 : : }
1840 : : else
1841 : 112 : next_state = DONE_ST;
1842 : :
1843 : 23417 : break;
1844 : :
1845 : : /* FRAC_ST means we are now parsing fractional digits. */
1846 : 14443 : case FRAC_ST:
1847 : 14443 : if (g_unichar_isdigit(uc))
1848 : : {
1849 : 11335 : int count = g_unichar_to_utf8(uc, out);
1850 : 11335 : out += count;
1851 : : }
1852 : 3108 : else if (uc == decimal_point)
1853 : : {
1854 : : /* If a subsequent decimal point is also whitespace,
1855 : : * assume it was intended as such and stop parsing.
1856 : : * Otherwise, there is a problem. */
1857 : 1 : if (g_unichar_isspace(decimal_point))
1858 : 0 : next_state = DONE_ST;
1859 : : else
1860 : 1 : next_state = NO_NUM_ST;
1861 : : }
1862 : 3107 : else if (uc == group_separator)
1863 : : {
1864 : : /* If a subsequent group separator is also whitespace,
1865 : : * assume it was intended as such and stop parsing.
1866 : : * Otherwise ignore it. */
1867 : 0 : if (g_unichar_isspace(group_separator))
1868 : 0 : next_state = DONE_ST;
1869 : : }
1870 : 3107 : else if (uc == ')' && need_paren)
1871 : : {
1872 : 0 : next_state = DONE_ST;
1873 : 0 : need_paren = FALSE;
1874 : : }
1875 : : else
1876 : 3107 : next_state = DONE_ST;
1877 : :
1878 : 14443 : break;
1879 : :
1880 : 0 : default:
1881 : 0 : PERR("bad state");
1882 : 0 : g_assert_not_reached();
1883 : : break;
1884 : : }
1885 : :
1886 : : /* If we're moving into the FRAC_ST or out of the machine
1887 : : * without going through FRAC_ST, record the integral value. */
1888 : 41098 : if (((next_state == FRAC_ST) && (state != FRAC_ST)) ||
1889 : 3219 : ((next_state == DONE_ST) && !got_decimal))
1890 : : {
1891 : 3220 : *out = '\0';
1892 : :
1893 : 3220 : if (*out_str && sscanf(out_str, "%" SCNd64, &numer) < 1)
1894 : 0 : next_state = NO_NUM_ST;
1895 : 3220 : else if (next_state == FRAC_ST)
1896 : : {
1897 : : /* reset the out pointer to record the fraction */
1898 : 3108 : out = out_str;
1899 : 3108 : *out = '\0';
1900 : :
1901 : 3108 : got_decimal = TRUE;
1902 : : }
1903 : : }
1904 : :
1905 : 41098 : state = next_state;
1906 : 41098 : if (done_state (state))
1907 : : break;
1908 : :
1909 : 37872 : in = g_utf8_next_char(in);
1910 : 37877 : }
1911 : :
1912 : : /* If there was an error, just quit */
1913 : 3226 : if (need_paren || (state == NO_NUM_ST))
1914 : : {
1915 : 7 : g_free(out_str);
1916 : 7 : return FALSE;
1917 : : }
1918 : :
1919 : : /* Cap the end of the fraction string, if any */
1920 : 3219 : *out = '\0';
1921 : :
1922 : : /* Add in fractional value */
1923 : 3219 : if (got_decimal && *out_str)
1924 : : {
1925 : :
1926 : 3011 : auto len = strlen(out_str);
1927 : :
1928 : 3011 : if (len > 12)
1929 : : {
1930 : 0 : out_str[12] = '\0';
1931 : 0 : len = 12;
1932 : : }
1933 : :
1934 : : int64_t fraction;
1935 : 3011 : if (sscanf (out_str, "%" SCNd64 , &fraction) < 1)
1936 : : {
1937 : 0 : g_free(out_str);
1938 : 0 : return FALSE;
1939 : : }
1940 : :
1941 : 3011 : denom = multiplier(len);
1942 : 3011 : numer *= denom;
1943 : 3011 : numer += fraction;
1944 : 3011 : }
1945 : 208 : else if (monetary && use_auto_decimal && !got_decimal)
1946 : : {
1947 : 0 : if ((auto_decimal_places > 0) && (auto_decimal_places <= 12))
1948 : : {
1949 : 0 : denom = multiplier(auto_decimal_places);
1950 : :
1951 : : /* No need to multiply numer by denom at this point,
1952 : : * since by specifying the auto decimal places the
1953 : : * user has effectively determined the scaling factor
1954 : : * for the numerator they entered.
1955 : : */
1956 : : }
1957 : : }
1958 : :
1959 : 3219 : if (result)
1960 : : {
1961 : 3219 : *result = gnc_numeric_create (numer, denom);
1962 : 3219 : if (is_negative)
1963 : 6 : *result = gnc_numeric_neg (*result);
1964 : : }
1965 : :
1966 : 3219 : if (endstr)
1967 : 133 : *endstr = (char* ) in;
1968 : :
1969 : 3219 : g_free (out_str);
1970 : :
1971 : 3219 : return TRUE;
1972 : : }
1973 : :
1974 : :
1975 : : static gboolean
1976 : 3206 : xaccParseAmountBasicInternal (const char* in_str, gboolean monetary,
1977 : : gboolean use_auto_decimal, gnc_numeric *result,
1978 : : char** endstr, gboolean skip)
1979 : : {
1980 : 3206 : struct lconv *lc = gnc_localeconv();
1981 : 3206 : gunichar negative_sign = g_utf8_get_char(lc->negative_sign);
1982 : :
1983 : : gunichar decimal_point;
1984 : : gunichar group_separator;
1985 : 3206 : if (monetary)
1986 : : {
1987 : 646 : group_separator = g_utf8_get_char(lc->mon_thousands_sep);
1988 : 646 : decimal_point = g_utf8_get_char(lc->mon_decimal_point);
1989 : : }
1990 : : else
1991 : : {
1992 : 2560 : group_separator = g_utf8_get_char(lc->thousands_sep);
1993 : 2560 : decimal_point = g_utf8_get_char(lc->decimal_point);
1994 : : }
1995 : :
1996 : 3206 : const char* ignore = nullptr;
1997 : 3206 : if (skip)
1998 : : {
1999 : : /* We want the locale's positive sign to be ignored.
2000 : : * If the locale doesn't specify one, we assume "+" as
2001 : : * an optional positive sign and ignore that */
2002 : 0 : ignore = lc->positive_sign;
2003 : 0 : if (!ignore || !*ignore)
2004 : 0 : ignore = "+";
2005 : : }
2006 : :
2007 : 3206 : return xaccParseAmountInternal(in_str, monetary, negative_sign,
2008 : : decimal_point, group_separator,
2009 : : ignore, use_auto_decimal,
2010 : 3206 : result, endstr);
2011 : : }
2012 : :
2013 : :
2014 : : gboolean
2015 : 3206 : xaccParseAmount (const char* in_str, gboolean monetary, gnc_numeric *result,
2016 : : char** endstr)
2017 : : {
2018 : 3206 : return xaccParseAmountBasicInternal (in_str, monetary, auto_decimal_enabled,
2019 : 3206 : result, endstr, FALSE);
2020 : : }
2021 : :
2022 : : gboolean
2023 : 0 : xaccParseAmountImport (const char* in_str, gboolean monetary,
2024 : : gnc_numeric *result,
2025 : : char** endstr, gboolean skip)
2026 : : {
2027 : 0 : return xaccParseAmountBasicInternal (in_str, monetary, FALSE,
2028 : 0 : result, endstr, skip);
2029 : : }
2030 : :
2031 : :
2032 : : gboolean
2033 : 10 : xaccParseAmountExtended (const char* in_str, gboolean monetary,
2034 : : gunichar negative_sign, gunichar decimal_point,
2035 : : gunichar group_separator, const char* ignore_list,
2036 : : gnc_numeric *result, char** endstr)
2037 : : {
2038 : 10 : return xaccParseAmountInternal (in_str, monetary, negative_sign,
2039 : : decimal_point, group_separator,
2040 : : ignore_list, auto_decimal_enabled,
2041 : 10 : result, endstr);
2042 : : }
2043 : :
2044 : : gboolean
2045 : 10 : xaccParseAmountExtImport (const char* in_str, gboolean monetary,
2046 : : gunichar negative_sign, gunichar decimal_point,
2047 : : gunichar group_separator, const char* ignore_list,
2048 : : gnc_numeric *result, char** endstr)
2049 : : {
2050 : 10 : return xaccParseAmountInternal (in_str, monetary, negative_sign,
2051 : : decimal_point, group_separator,
2052 : : ignore_list, FALSE,
2053 : 10 : result, endstr);
2054 : : }
2055 : :
2056 : :
2057 : : /* enable/disable the auto_decimal_enabled option */
2058 : : static void
2059 : 0 : gnc_set_auto_decimal_enabled (gpointer settings, char* key, gpointer user_data)
2060 : : {
2061 : 0 : auto_decimal_enabled =
2062 : 0 : gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT);
2063 : 0 : }
2064 : :
2065 : : /* set the number of auto decimal places to use */
2066 : : static void
2067 : 0 : gnc_set_auto_decimal_places (gpointer settings, char* key, gpointer user_data)
2068 : : {
2069 : 0 : auto_decimal_places =
2070 : 0 : gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES);
2071 : 0 : }
2072 : :
2073 : : static void
2074 : 0 : gnc_auto_decimal_init (void)
2075 : : {
2076 : 0 : auto_decimal_enabled =
2077 : 0 : gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT);
2078 : 0 : auto_decimal_places =
2079 : 0 : gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES);
2080 : 0 : }
2081 : :
2082 : : void
2083 : 0 : gnc_ui_util_init (void)
2084 : : {
2085 : 0 : gnc_configure_account_separator ();
2086 : 0 : gnc_auto_decimal_init();
2087 : :
2088 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNT_SEPARATOR,
2089 : : (void*)gnc_configure_account_separator, nullptr);
2090 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_NONE,
2091 : : (void*)gnc_configure_reverse_balance, nullptr);
2092 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_CREDIT,
2093 : : (void*)gnc_configure_reverse_balance, nullptr);
2094 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_INC_EXP,
2095 : : (void*)gnc_configure_reverse_balance, nullptr);
2096 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_CHOICE_LOCALE,
2097 : : (void*)gnc_currency_changed_cb, nullptr);
2098 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_CHOICE_OTHER,
2099 : : (void*)gnc_currency_changed_cb, nullptr);
2100 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_OTHER,
2101 : : (void*)gnc_currency_changed_cb, nullptr);
2102 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_CHOICE_LOCALE,
2103 : : (void*)gnc_currency_changed_cb, nullptr);
2104 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_CHOICE_OTHER,
2105 : : (void*)gnc_currency_changed_cb, nullptr);
2106 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_OTHER,
2107 : : (void*)gnc_currency_changed_cb, nullptr);
2108 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT,
2109 : : (void*)gnc_set_auto_decimal_enabled, nullptr);
2110 : 0 : gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES,
2111 : : (void*)gnc_set_auto_decimal_places, nullptr);
2112 : :
2113 : 0 : }
2114 : :
2115 : : void
2116 : 0 : gnc_ui_util_remove_registered_prefs (void)
2117 : : {
2118 : : // remove the registered pref call backs above
2119 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2120 : : GNC_PREF_ACCOUNT_SEPARATOR,
2121 : : (void*)gnc_configure_account_separator, nullptr);
2122 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2123 : : GNC_PREF_REVERSED_ACCTS_NONE,
2124 : : (void*)gnc_configure_reverse_balance, nullptr);
2125 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2126 : : GNC_PREF_REVERSED_ACCTS_CREDIT,
2127 : : (void*)gnc_configure_reverse_balance, nullptr);
2128 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2129 : : GNC_PREF_REVERSED_ACCTS_INC_EXP,
2130 : : (void*)gnc_configure_reverse_balance, nullptr);
2131 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2132 : : GNC_PREF_CURRENCY_CHOICE_LOCALE,
2133 : : (void*)gnc_currency_changed_cb, nullptr);
2134 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2135 : : GNC_PREF_CURRENCY_CHOICE_OTHER,
2136 : : (void*)gnc_currency_changed_cb, nullptr);
2137 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2138 : : GNC_PREF_CURRENCY_OTHER,
2139 : : (void*)gnc_currency_changed_cb, nullptr);
2140 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
2141 : : GNC_PREF_CURRENCY_CHOICE_LOCALE,
2142 : : (void*)gnc_currency_changed_cb, nullptr);
2143 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
2144 : : GNC_PREF_CURRENCY_CHOICE_OTHER,
2145 : : (void*)gnc_currency_changed_cb, nullptr);
2146 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
2147 : : GNC_PREF_CURRENCY_OTHER,
2148 : : (void*)gnc_currency_changed_cb, nullptr);
2149 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2150 : : GNC_PREF_AUTO_DECIMAL_POINT,
2151 : : (void*)gnc_set_auto_decimal_enabled, nullptr);
2152 : 0 : gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2153 : : GNC_PREF_AUTO_DECIMAL_PLACES,
2154 : : (void*)gnc_set_auto_decimal_places, nullptr);
2155 : 0 : }
2156 : :
2157 : : char*
2158 : 0 : gnc_filter_text_for_bidi_marks (const char* text)
2159 : : {
2160 : 0 : if (!text)
2161 : 0 : return nullptr;
2162 : :
2163 : 0 : int32_t len = static_cast<int32_t> (strlen (text));
2164 : 0 : std::string result;
2165 : 0 : result.reserve (len);
2166 : :
2167 : 0 : const char* p = text;
2168 : 0 : int32_t i = 0;
2169 : :
2170 : 0 : while (i < len)
2171 : : {
2172 : : UChar32 c;
2173 : 0 : int32_t start = i;
2174 : 0 : U8_NEXT (p, i, len, c);
2175 : :
2176 : 0 : if (c >= 0 && !u_hasBinaryProperty (c, UCHAR_BIDI_CONTROL))
2177 : 0 : result.append (p + start, i - start);
2178 : : }
2179 : :
2180 : 0 : return g_strdup (result.c_str ());
2181 : 0 : }
2182 : :
2183 : : static inline bool
2184 : 0 : unichar_is_cntrl (gunichar uc)
2185 : : {
2186 : 0 : return (uc < 0x20 || (uc > 0x7e && uc < 0xa0));
2187 : : }
2188 : :
2189 : : char*
2190 : 0 : gnc_filter_text_for_control_chars (const char* text)
2191 : : {
2192 : 0 : bool cntrl = false;
2193 : 0 : bool text_found = false;
2194 : :
2195 : 0 : if (!text)
2196 : 0 : return nullptr;
2197 : :
2198 : 0 : if (!g_utf8_validate (text, -1, nullptr))
2199 : 0 : return nullptr;
2200 : :
2201 : 0 : auto filtered = g_string_sized_new (strlen (text) + 1);
2202 : :
2203 : 0 : auto ch = text;
2204 : :
2205 : 0 : while (*ch)
2206 : : {
2207 : 0 : auto uc = g_utf8_get_char (ch);
2208 : :
2209 : : // check for starting with control characters
2210 : 0 : if (unichar_is_cntrl (uc) && !text_found)
2211 : : {
2212 : 0 : ch = g_utf8_next_char (ch);
2213 : 0 : continue;
2214 : : }
2215 : : // check for alpha, num and punctuation
2216 : 0 : if (!unichar_is_cntrl (uc))
2217 : : {
2218 : 0 : filtered = g_string_append_unichar (filtered, uc);
2219 : 0 : text_found = true;
2220 : : }
2221 : : // check for control characters after text
2222 : 0 : if (unichar_is_cntrl (uc))
2223 : 0 : cntrl = true;
2224 : :
2225 : 0 : ch = g_utf8_next_char (ch);
2226 : :
2227 : 0 : if (cntrl) // if control characters in text replace with space
2228 : : {
2229 : 0 : auto uc2 = g_utf8_get_char (ch);
2230 : :
2231 : 0 : if (!unichar_is_cntrl (uc2))
2232 : 0 : filtered = g_string_append_unichar (filtered, ' ');
2233 : : }
2234 : 0 : cntrl = false;
2235 : : }
2236 : 0 : return g_string_free (filtered, FALSE);
2237 : : }
2238 : :
2239 : : void
2240 : 0 : gnc_filter_text_set_cursor_position (const char* incoming_text,
2241 : : const char* symbol,
2242 : : gint *cursor_position)
2243 : : {
2244 : 0 : int num = 0;
2245 : :
2246 : 0 : if (*cursor_position == 0)
2247 : 0 : return;
2248 : :
2249 : 0 : if (!incoming_text || !symbol)
2250 : 0 : return;
2251 : :
2252 : 0 : if (g_strrstr (incoming_text, symbol) == nullptr)
2253 : 0 : return;
2254 : :
2255 : 0 : auto text_len = g_utf8_strlen (incoming_text, -1);
2256 : :
2257 : 0 : for (int x = 0; x < text_len; x++)
2258 : : {
2259 : 0 : auto temp = g_utf8_offset_to_pointer (incoming_text, x);
2260 : :
2261 : 0 : if (g_str_has_prefix (temp, symbol))
2262 : 0 : num++;
2263 : :
2264 : 0 : if (g_strrstr (temp, symbol) == nullptr)
2265 : 0 : break;
2266 : : }
2267 : 0 : *cursor_position = *cursor_position - (num * g_utf8_strlen (symbol, -1));
2268 : : }
2269 : :
2270 : : char*
2271 : 0 : gnc_filter_text_for_currency_symbol (const char* incoming_text,
2272 : : const char* symbol)
2273 : : {
2274 : 0 : if (!incoming_text)
2275 : 0 : return nullptr;
2276 : :
2277 : 0 : if (!symbol)
2278 : 0 : return g_strdup (incoming_text);
2279 : :
2280 : 0 : if (g_strrstr (incoming_text, symbol) == nullptr)
2281 : 0 : return g_strdup (incoming_text);
2282 : :
2283 : 0 : auto split = g_strsplit (incoming_text, symbol, -1);
2284 : :
2285 : 0 : auto ret_text = g_strjoinv (nullptr, split);
2286 : :
2287 : 0 : g_strfreev (split);
2288 : 0 : return ret_text;
2289 : : }
2290 : :
2291 : : char*
2292 : 0 : gnc_filter_text_for_currency_commodity (const gnc_commodity *comm,
2293 : : const char* incoming_text,
2294 : : const char** symbol)
2295 : : {
2296 : 0 : if (!incoming_text)
2297 : : {
2298 : 0 : *symbol = nullptr;
2299 : 0 : return nullptr;
2300 : : }
2301 : :
2302 : 0 : if (!gnc_commodity_is_currency (comm))
2303 : : {
2304 : 0 : *symbol = nullptr;
2305 : 0 : return g_strdup (incoming_text);
2306 : : }
2307 : :
2308 : 0 : if (comm)
2309 : 0 : *symbol = gnc_commodity_get_nice_symbol (comm);
2310 : : else
2311 : 0 : *symbol = gnc_commodity_get_nice_symbol (gnc_default_currency ());
2312 : :
2313 : 0 : return gnc_filter_text_for_currency_symbol (incoming_text, *symbol);
2314 : : }
2315 : :
2316 : : gchar*
2317 : 0 : gnc_list_formatter (GList *strings)
2318 : : {
2319 : 0 : g_return_val_if_fail (strings, nullptr);
2320 : :
2321 : 0 : UErrorCode status = U_ZERO_ERROR;
2322 : 0 : auto formatter = icu::ListFormatter::createInstance(status);
2323 : 0 : std::vector<UniStr> strvec;
2324 : 0 : UniStr result;
2325 : 0 : std::string retval;
2326 : :
2327 : 0 : for (auto n = strings; n; n = g_list_next (n))
2328 : : {
2329 : 0 : auto utf8_str{static_cast<const char*>(n->data)};
2330 : 0 : strvec.push_back (UniStr::fromUTF8(utf8_str));
2331 : : }
2332 : :
2333 : 0 : formatter->format (strvec.data(), strvec.size(), result, status);
2334 : :
2335 : 0 : if (U_FAILURE(status))
2336 : 0 : PERR ("Unicode error");
2337 : : else
2338 : 0 : result.toUTF8String(retval);
2339 : :
2340 : 0 : delete formatter;
2341 : 0 : return g_strdup (retval.c_str());
2342 : 0 : }
|