+ /* 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)
+{
+ 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 };
+}
+
+struct fmt_number_style *
+fmt_number_style_from_string (const char *s)
+{
+ 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;