#include "gl/c-strcase.h"
#include "gl/minmax.h"
#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
-struct fmt_settings
- {
- struct fmt_number_style styles[FMT_NUMBER_OF_FORMATS];
- };
-
bool is_fmt_type (enum fmt_type);
static bool valid_width (enum fmt_type, int width, enum fmt_use);
static void fmt_clamp_width (struct fmt_spec *, enum fmt_use);
static void fmt_clamp_decimals (struct fmt_spec *, enum fmt_use);
-static void fmt_affix_set (struct fmt_affix *, const char *);
-static void fmt_affix_free (struct fmt_affix *);
-
-static void fmt_number_style_init (struct fmt_number_style *);
-static void fmt_number_style_clone (struct fmt_number_style *,
- const struct fmt_number_style *);
-static void fmt_number_style_destroy (struct fmt_number_style *);
-
-/* Creates and returns a new struct fmt_settings with default format styles. */
-struct fmt_settings *
-fmt_settings_create (void)
+void
+fmt_settings_init (struct fmt_settings *settings)
{
- struct fmt_settings *settings;
- int t;
-
- settings = xzalloc (sizeof *settings);
- for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t)
- fmt_number_style_init (&settings->styles[t]);
- fmt_settings_set_decimal (settings, '.');
-
- return settings;
+ *settings = (struct fmt_settings) FMT_SETTINGS_INIT;
}
-/* Destroys SETTINGS. */
void
-fmt_settings_destroy (struct fmt_settings *settings)
+fmt_settings_uninit (struct fmt_settings *settings)
{
- if (settings != NULL)
- {
- int t;
-
- for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t)
- fmt_number_style_destroy (&settings->styles[t]);
-
- free (settings->styles);
- }
+ for (int i = 0; i < FMT_N_CCS; i++)
+ fmt_number_style_destroy (settings->ccs[i]);
}
-/* Returns a copy of SETTINGS. */
-struct fmt_settings *
-fmt_settings_clone (const struct fmt_settings *old)
+void
+fmt_settings_copy (struct fmt_settings *new, const struct fmt_settings *old)
{
- struct fmt_settings *new;
- int t;
-
- new = xmalloc (sizeof *new);
- for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t)
- fmt_number_style_clone (&new->styles[t], &old->styles[t]);
+ new->decimal = old->decimal;
+ for (int i = 0; i < FMT_N_CCS; i++)
+ new->ccs[i] = fmt_number_style_clone (old->ccs[i]);
+}
- return new;
+static size_t
+fmt_type_to_cc_index (enum fmt_type type)
+{
+ switch (type)
+ {
+ case FMT_CCA: return 0;
+ case FMT_CCB: return 1;
+ case FMT_CCC: return 2;
+ case FMT_CCD: return 3;
+ case FMT_CCE: return 4;
+ default: NOT_REACHED ();
+ }
}
/* Returns the number formatting style associated with the given
fmt_settings_get_style (const struct fmt_settings *settings,
enum fmt_type type)
{
- assert (is_fmt_type (type));
- return &settings->styles[type];
-}
+ verify (FMT_F < 6);
+ verify (FMT_COMMA < 6);
+ verify (FMT_DOT < 6);
+ verify (FMT_DOLLAR < 6);
+ verify (FMT_PCT < 6);
+ verify (FMT_E < 6);
+
+#define OPPOSITE(C) ((C) == ',' ? '.' : ',')
+#define AFFIX(S) { .s = (char *) (S), .width = sizeof (S) - 1 }
+#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING) { \
+ .neg_prefix = AFFIX ("-"), \
+ .prefix = AFFIX (PREFIX), \
+ .suffix = AFFIX (SUFFIX), \
+ .neg_suffix = AFFIX (""), \
+ .decimal = DECIMAL, \
+ .grouping = GROUPING, \
+ }
+#define ANS(DECIMAL, GROUPING) { \
+ [FMT_F] = NS( "", "", DECIMAL, 0), \
+ [FMT_E] = NS( "", "", DECIMAL, 0), \
+ [FMT_COMMA] = NS( "", "", DECIMAL, GROUPING), \
+ [FMT_DOT] = NS( "", "", GROUPING, DECIMAL), \
+ [FMT_DOLLAR] = NS("$", "", DECIMAL, GROUPING), \
+ [FMT_PCT] = NS( "", "%", DECIMAL, 0), \
+ }
+
+ static const struct fmt_number_style period_styles[6] = ANS ('.', ',');
+ static const struct fmt_number_style comma_styles[6] = ANS (',', '.');
+ static const struct fmt_number_style default_style = NS ("", "", '.', 0);
-/* Sets the number style for TYPE to have the given DECIMAL and GROUPING
- characters, negative prefix NEG_PREFIX, prefix PREFIX, suffix SUFFIX, and
- negative suffix NEG_SUFFIX. All of the strings are UTF-8 encoded. */
-void
-fmt_settings_set_style (struct fmt_settings *settings, enum fmt_type type,
- char decimal, char grouping,
- const char *neg_prefix, const char *prefix,
- const char *suffix, const char *neg_suffix)
-{
- struct fmt_number_style *style = &settings->styles[type];
- int total_bytes, total_width;
-
- assert (grouping == '.' || grouping == ',' || grouping == 0);
- assert (decimal == '.' || decimal == ',');
- assert (decimal != grouping);
-
- fmt_number_style_destroy (style);
-
- fmt_affix_set (&style->neg_prefix, neg_prefix);
- fmt_affix_set (&style->prefix, prefix);
- fmt_affix_set (&style->suffix, suffix);
- fmt_affix_set (&style->neg_suffix, neg_suffix);
- style->decimal = decimal;
- style->grouping = grouping;
-
- total_bytes = (strlen (neg_prefix) + strlen (prefix)
- + strlen (suffix) + strlen (neg_suffix));
- total_width = (style->neg_prefix.width + style->prefix.width
- + style->suffix.width + style->neg_suffix.width);
- style->extra_bytes = MAX (0, total_bytes - total_width);
-}
+ switch (type)
+ {
+ case FMT_F:
+ case FMT_COMMA:
+ case FMT_DOT:
+ case FMT_DOLLAR:
+ case FMT_PCT:
+ case FMT_E:
+ return (settings->decimal == '.'
+ ? &period_styles[type]
+ : &comma_styles[type]);
+
+ case FMT_CCA:
+ case FMT_CCB:
+ case FMT_CCC:
+ case FMT_CCD:
+ case FMT_CCE:
+ {
+ size_t idx = fmt_type_to_cc_index (type);
+ return settings->ccs[idx] ? settings->ccs[idx] : &default_style;
+ }
-/* Sets the decimal point character for the settings in S to DECIMAL.
+ default:
+ return &default_style;
+ }
+}
- This has no effect on custom currency formats. */
void
-fmt_settings_set_decimal (struct fmt_settings *s, char decimal)
+fmt_settings_set_cc (struct fmt_settings *settings, enum fmt_type type,
+ struct fmt_number_style *style)
{
- int grouping = decimal == '.' ? ',' : '.';
- assert (decimal == '.' || decimal == ',');
-
- fmt_settings_set_style (s, FMT_F, decimal, 0, "-", "", "", "");
- fmt_settings_set_style (s, FMT_E, decimal, 0, "-", "", "", "");
- fmt_settings_set_style (s, FMT_COMMA, decimal, grouping, "-", "", "", "");
- fmt_settings_set_style (s, FMT_DOT, grouping, decimal, "-", "", "", "");
- fmt_settings_set_style (s, FMT_DOLLAR, decimal, grouping, "-", "$", "", "");
- fmt_settings_set_style (s, FMT_PCT, decimal, 0, "-", "", "%", "");
+ size_t idx = fmt_type_to_cc_index (type);
+
+ fmt_number_style_destroy (settings->ccs[idx]);
+ settings->ccs[idx] = style;
}
+\f
/* Returns an input format specification with type TYPE, width W,
and D decimals. */
struct fmt_spec
fmt->d = max_d;
}
\f
-/* Sets AFFIX's string value to S, a UTF-8 encoded string. */
-static void
-fmt_affix_set (struct fmt_affix *affix, const char *s)
+static struct fmt_affix
+fmt_affix_clone (const struct fmt_affix *old)
{
- affix->s = s[0] == '\0' ? CONST_CAST (char *, "") : xstrdup (s);
- affix->width = u8_strwidth (CHAR_CAST (const uint8_t *, s), "UTF-8");
+ return (struct fmt_affix) {
+ .s = old->s ? xstrdup (old->s) : NULL,
+ .width = old->width,
+ };
}
/* Frees data in AFFIX. */
free (affix->s);
}
-static void
-fmt_number_style_init (struct fmt_number_style *style)
+/* Find and returns the grouping character in CC_STRING (either '.' or ',') or
+ 0 on error. */
+static int
+find_cc_separators (const char *cc_string)
+{
+ /* Count commas and periods. There must be exactly three of
+ one or the other, except that an apostrophe escapes a
+ following comma or period. */
+ int n_commas = 0;
+ int n_dots = 0;
+ for (const char *p = cc_string; *p; p++)
+ if (*p == ',')
+ n_commas++;
+ else if (*p == '.')
+ n_dots++;
+ else if (*p == '\'' && (p[1] == '.' || p[1] == ',' || p[1] == '\''))
+ p++;
+
+ return (n_commas == 3 ? (n_dots != 3 ? ',' : 0)
+ : n_dots == 3 ? '.'
+ : 0);
+}
+
+/* Extracts a token from IN into a newly allocated string AFFIXP. Tokens are
+ delimited by GROUPING. Returns the first character following the token. */
+static struct fmt_affix
+extract_cc_token (const char **sp, int grouping, size_t *extra_bytes)
{
- fmt_affix_set (&style->neg_prefix, "");
- fmt_affix_set (&style->prefix, "");
- fmt_affix_set (&style->suffix, "");
- fmt_affix_set (&style->neg_suffix, "");
- style->decimal = '.';
- style->grouping = 0;
+ const char *p = *sp;
+ for (; *p && *p != grouping; p++)
+ if (*p == '\'' && p[1] == grouping)
+ p++;
+
+ size_t length = p - *sp;
+ char *affix = xmemdup0 (*sp, length);
+ size_t width = u8_strwidth (CHAR_CAST (const uint8_t *, affix), "UTF-8");
+ if (length > width)
+ *extra_bytes += length - width;
+
+ *sp = p + (*p != 0);
+
+ return (struct fmt_affix) { .s = affix, .width = width };
}
-static void
-fmt_number_style_clone (struct fmt_number_style *new,
- const struct fmt_number_style *old)
+struct fmt_number_style *
+fmt_number_style_from_string (const char *s)
{
- fmt_affix_set (&new->neg_prefix, old->neg_prefix.s);
- fmt_affix_set (&new->prefix, old->prefix.s);
- fmt_affix_set (&new->suffix, old->suffix.s);
- fmt_affix_set (&new->neg_suffix, old->neg_suffix.s);
- new->decimal = old->decimal;
- new->grouping = old->grouping;
- new->extra_bytes = old->extra_bytes;
+ char grouping = find_cc_separators (s);
+ if (!grouping)
+ return NULL;
+
+ size_t extra_bytes = 0;
+ struct fmt_affix neg_prefix = extract_cc_token (&s, grouping, &extra_bytes);
+ struct fmt_affix prefix = extract_cc_token (&s, grouping, &extra_bytes);
+ struct fmt_affix suffix = extract_cc_token (&s, grouping, &extra_bytes);
+ struct fmt_affix neg_suffix = extract_cc_token (&s, grouping, &extra_bytes);
+
+ struct fmt_number_style *style = xmalloc (sizeof *style);
+ *style = (struct fmt_number_style) {
+ .neg_prefix = neg_prefix,
+ .prefix = prefix,
+ .suffix = suffix,
+ .neg_suffix = neg_suffix,
+ .decimal = grouping == '.' ? ',' : '.',
+ .grouping = grouping,
+ .extra_bytes = extra_bytes,
+ };
+ return style;
+}
+
+struct fmt_number_style *
+fmt_number_style_clone (const struct fmt_number_style *old)
+{
+ if (old)
+ {
+ struct fmt_number_style *new = xmalloc (sizeof *new);
+ *new = (struct fmt_number_style) {
+ .neg_prefix = fmt_affix_clone (&old->neg_prefix),
+ .prefix = fmt_affix_clone (&old->prefix),
+ .suffix = fmt_affix_clone (&old->suffix),
+ .neg_suffix = fmt_affix_clone (&old->neg_suffix),
+ .decimal = old->decimal,
+ .grouping = old->grouping,
+ .extra_bytes = old->extra_bytes,
+ };
+ return new;
+ }
+ else
+ return NULL;
}
/* Destroys a struct fmt_number_style. */
-static void
+void
fmt_number_style_destroy (struct fmt_number_style *style)
{
if (style != NULL)
fmt_affix_free (&style->prefix);
fmt_affix_free (&style->suffix);
fmt_affix_free (&style->neg_suffix);
+ free (style);
}
}
int global_algorithm;
int syntax;
- struct fmt_settings *styles;
+ struct fmt_settings styles;
enum settings_output_devices output_routing[SETTINGS_N_OUTPUT_TYPES];
ENHANCED, /* cmd_algorithm */
ENHANCED, /* global_algorithm */
ENHANCED, /* syntax */
- NULL, /* styles */
+ FMT_SETTINGS_INIT, /* styles */
/* output_routing */
{SETTINGS_DEVICE_LISTING | SETTINGS_DEVICE_TERMINAL,
settings_init (void)
{
settings_set_epoch (-1);
- the_settings.styles = fmt_settings_create ();
-
settings_set_decimal_char (get_system_decimal ());
}
settings_copy (struct settings *dst, const struct settings *src)
{
*dst = *src;
- dst->styles = fmt_settings_clone (src->styles);
+ fmt_settings_copy (&dst->styles, &src->styles);
}
/* Returns a copy of the current settings. */
{
if (s != NULL)
{
- fmt_settings_destroy (s->styles);
+ fmt_settings_uninit (&s->styles);
if (s != &the_settings)
free (s);
}
}
\f
-
-/* Find the grouping characters in CC_STRING and sets *GROUPING and *DECIMAL
- appropriately. Returns true if successful, false otherwise. */
-static bool
-find_cc_separators (const char *cc_string, char *decimal, char *grouping)
-{
- const char *sp;
- int comma_cnt, dot_cnt;
-
- /* Count commas and periods. There must be exactly three of
- one or the other, except that an apostrophe escapes a
- following comma or period. */
- comma_cnt = dot_cnt = 0;
- for (sp = cc_string; *sp; sp++)
- if (*sp == ',')
- comma_cnt++;
- else if (*sp == '.')
- dot_cnt++;
- else if (*sp == '\'' && (sp[1] == '.' || sp[1] == ',' || sp[1] == '\''))
- sp++;
-
- if ((comma_cnt == 3) == (dot_cnt == 3))
- return false;
-
- if (comma_cnt == 3)
- {
- *decimal = '.';
- *grouping = ',';
- }
- else
- {
- *decimal = ',';
- *grouping = '.';
- }
- return true;
-}
-
-/* Extracts a token from IN into a newly allocated string AFFIXP. Tokens are
- delimited by GROUPING. Returns the first character following the token. */
-static const char *
-extract_cc_token (const char *in, int grouping, char **affixp)
-{
- char *out;
-
- out = *affixp = xmalloc (strlen (in) + 1);
- for (; *in != '\0' && *in != grouping; in++)
- {
- if (*in == '\'' && in[1] == grouping)
- in++;
- *out++ = *in;
- }
- *out = '\0';
-
- if (*in == grouping)
- in++;
- return in;
-}
-
/* Sets custom currency specifier CC having name CC_NAME ('A' through
'E') to correspond to the settings in CC_STRING. */
bool
settings_set_cc (const char *cc_string, enum fmt_type type)
{
- char *neg_prefix, *prefix, *suffix, *neg_suffix;
- char decimal, grouping;
-
- assert (fmt_get_category (type) == FMT_CAT_CUSTOM);
-
- /* Determine separators. */
- if (!find_cc_separators (cc_string, &decimal, &grouping))
+ struct fmt_number_style *style = fmt_number_style_from_string (cc_string);
+ if (!style)
{
msg (SE, _("%s: Custom currency string `%s' does not contain "
"exactly three periods or commas (or it contains both)."),
return false;
}
- cc_string = extract_cc_token (cc_string, grouping, &neg_prefix);
- cc_string = extract_cc_token (cc_string, grouping, &prefix);
- cc_string = extract_cc_token (cc_string, grouping, &suffix);
- cc_string = extract_cc_token (cc_string, grouping, &neg_suffix);
-
- fmt_settings_set_style (the_settings.styles, type, decimal, grouping,
- neg_prefix, prefix, suffix, neg_suffix);
-
- free (neg_suffix);
- free (suffix);
- free (prefix);
- free (neg_prefix);
-
+ fmt_settings_set_cc (&the_settings.styles, type, style);
return true;
}
int
settings_get_decimal_char (enum fmt_type type)
{
- return fmt_settings_get_style (the_settings.styles, type)->decimal;
+ return fmt_settings_get_style (&the_settings.styles, type)->decimal;
}
void
settings_set_decimal_char (char decimal)
{
- fmt_settings_set_decimal (the_settings.styles, decimal);
+ the_settings.styles.decimal = decimal;
}
/* Returns the number formatting style associated with the given
settings_get_style (enum fmt_type type)
{
assert (is_fmt_type (type));
- return fmt_settings_get_style (the_settings.styles, type);
+ return fmt_settings_get_style (&the_settings.styles, type);
}
/* Returns a string of the form "$#,###.##" according to FMT,
assert (fmt->type == FMT_DOLLAR);
- fns = fmt_settings_get_style (the_settings.styles, fmt->type);
+ fns = fmt_settings_get_style (&the_settings.styles, fmt->type);
ds_put_byte (&str, '$');
for (c = MAX (fmt->w - fmt->d - 1, 0); c > 0;)