+
+/* Adjusts FMT's width and decimal places to be valid for USE. */
+void
+fmt_fix (struct fmt_spec *fmt, enum fmt_use use)
+{
+ /* Clamp width to those allowed by format. */
+ fmt_clamp_width (fmt, use);
+
+ /* If FMT has more decimal places than allowed, attempt to increase FMT's
+ width until that number of decimal places can be achieved. */
+ if (fmt->d > fmt_max_decimals (fmt->type, fmt->w, use)
+ && fmt_takes_decimals (fmt->type))
+ {
+ int max_w = fmt_max_width (fmt->type, use);
+ for (; fmt->w < max_w; fmt->w++)
+ if (fmt->d <= fmt_max_decimals (fmt->type, fmt->w, use))
+ break;
+ }
+
+ /* Clamp decimals to those allowed by format and width. */
+ fmt_clamp_decimals (fmt, use);
+}
+
+/* Adjusts FMT's width and decimal places to be valid for an
+ input format. */
+void
+fmt_fix_input (struct fmt_spec *fmt)
+{
+ fmt_fix (fmt, FMT_FOR_INPUT);
+}
+
+/* Adjusts FMT's width and decimal places to be valid for an
+ output format. */
+void
+fmt_fix_output (struct fmt_spec *fmt)
+{
+ fmt_fix (fmt, FMT_FOR_OUTPUT);
+}
+
+/* Sets FMT's width to WIDTH (or the nearest width allowed by FMT's type) and
+ reduces its decimal places as necessary (if necessary) for that width. */
+void
+fmt_change_width (struct fmt_spec *fmt, int width, enum fmt_use use)
+{
+ fmt->w = width;
+ fmt_clamp_width (fmt, use);
+ fmt_clamp_decimals (fmt, use);
+}
+
+/* Sets FMT's decimal places to DECIMALS (or the nearest number of decimal
+ places allowed by FMT's type) and increases its width as necessary (if
+ necessary) for that number of decimal places. */
+void
+fmt_change_decimals (struct fmt_spec *fmt, int decimals, enum fmt_use use)
+{
+ fmt->d = decimals;
+ fmt_fix (fmt, use);
+}
+\f
+/* Describes a display format. */
+struct fmt_desc
+ {
+ char name[9];
+ int min_input_width, min_output_width;
+ int io;
+ enum fmt_category category;
+ };
+
+static const struct fmt_desc *get_fmt_desc (enum fmt_type type);
+
+/* Returns the name of the given format TYPE. */
+const char *
+fmt_name (enum fmt_type type)
+{
+ return get_fmt_desc (type)->name;
+}
+
+/* Tries to parse NAME as a format type.
+ If successful, stores the type in *TYPE and returns true.
+ On failure, returns false. */
+bool
+fmt_from_name (const char *name, enum fmt_type *type)
+{
+ int i;
+
+ for (i = 0; i < FMT_NUMBER_OF_FORMATS; i++)
+ if (!c_strcasecmp (name, get_fmt_desc (i)->name))
+ {
+ *type = i;
+ return true;
+ }
+ return false;
+}
+
+/* Returns true if TYPE accepts decimal places,
+ false otherwise. */
+bool
+fmt_takes_decimals (enum fmt_type type)
+{
+ return fmt_max_output_decimals (type, fmt_max_output_width (type)) > 0;
+}
+
+/* Returns the minimum width of the given format TYPE for the given USE. */
+int
+fmt_min_width (enum fmt_type type, enum fmt_use use)
+{
+ return (use == FMT_FOR_INPUT
+ ? fmt_min_input_width (type)
+ : fmt_min_output_width (type));
+}
+
+/* Returns the maximum width of the given format TYPE,
+ for input if FOR_INPUT is true,
+ for output otherwise. */
+int
+fmt_max_width (enum fmt_type type, enum fmt_use use UNUSED)
+{
+ /* Maximum width is actually invariant of whether the format is
+ for input or output, so FOR_INPUT is unused. */
+ assert (is_fmt_type (type));
+ switch (type)
+ {
+ case FMT_P:
+ case FMT_PK:
+ case FMT_PIBHEX:
+ case FMT_RBHEX:
+ return 16;
+
+ case FMT_IB:
+ case FMT_PIB:
+ case FMT_RB:
+ return 8;
+
+ case FMT_A:
+ return MAX_STRING;
+
+ case FMT_AHEX:
+ return 2 * MAX_STRING;
+
+ default:
+ return 40;
+ }
+}
+
+/* Returns the maximum number of decimal places allowed for the
+ given format TYPE with a width of WIDTH places, for the given USE. */
+int
+fmt_max_decimals (enum fmt_type type, int width, enum fmt_use use)
+{
+ int max_d;
+
+ switch (type)
+ {
+ case FMT_F:
+ case FMT_COMMA:
+ case FMT_DOT:
+ max_d = use == FMT_FOR_INPUT ? width : width - 1;
+ break;
+
+ case FMT_DOLLAR:
+ case FMT_PCT:
+ max_d = use == FMT_FOR_INPUT ? width : width - 2;
+ break;
+
+ case FMT_E:
+ max_d = use == FMT_FOR_INPUT ? width : width - 7;
+ break;
+
+ case FMT_CCA:
+ case FMT_CCB:
+ case FMT_CCC:
+ case FMT_CCD:
+ case FMT_CCE:
+ assert (use == FMT_FOR_OUTPUT);
+ max_d = width - 1;
+ break;
+
+ case FMT_N:
+ case FMT_Z:
+ max_d = width;
+ break;
+
+ case FMT_P:
+ max_d = width * 2 - 1;
+ break;
+
+ case FMT_PK:
+ max_d = width * 2;
+ break;
+
+ case FMT_IB:
+ case FMT_PIB:
+ max_d = max_digits_for_bytes (width);
+ break;
+
+ case FMT_PIBHEX:
+ max_d = 0;
+ break;
+
+ case FMT_RB:
+ case FMT_RBHEX:
+ max_d = 16;
+ break;
+
+ case FMT_DATE:
+ case FMT_ADATE:
+ case FMT_EDATE:
+ case FMT_JDATE:
+ case FMT_SDATE:
+ case FMT_QYR:
+ case FMT_MOYR:
+ case FMT_WKYR:
+ max_d = 0;
+ break;
+
+ case FMT_DATETIME:
+ max_d = width - 21;
+ break;
+
+ case FMT_YMDHMS:
+ max_d = width - 20;
+ break;
+
+ case FMT_MTIME:
+ max_d = width - 6;
+ break;
+
+ case FMT_TIME:
+ max_d = width - 9;
+ break;
+
+ case FMT_DTIME:
+ max_d = width - 12;
+ break;
+
+ case FMT_WKDAY:
+ case FMT_MONTH:
+ case FMT_A:
+ case FMT_AHEX:
+ max_d = 0;
+ break;
+
+ default:
+ NOT_REACHED ();
+ }
+
+ if (max_d < 0)
+ max_d = 0;
+ else if (max_d > 16)
+ max_d = 16;
+ return max_d;
+}
+
+/* Returns the minimum acceptable width for an input field
+ formatted with the given TYPE. */
+int
+fmt_min_input_width (enum fmt_type type)
+{
+ return get_fmt_desc (type)->min_input_width;
+}
+
+/* Returns the maximum acceptable width for an input field
+ formatted with the given TYPE. */
+int
+fmt_max_input_width (enum fmt_type type)
+{
+ return fmt_max_width (type, FMT_FOR_INPUT);
+}
+
+/* Returns the maximum number of decimal places allowed in an
+ input field of the given TYPE and WIDTH. */
+int
+fmt_max_input_decimals (enum fmt_type type, int width)
+{
+ assert (valid_width (type, width, true));
+ return fmt_max_decimals (type, width, FMT_FOR_INPUT);
+}
+
+/* Returns the minimum acceptable width for an output field
+ formatted with the given TYPE. */
+int
+fmt_min_output_width (enum fmt_type type)
+{
+ return get_fmt_desc (type)->min_output_width;
+}
+
+/* Returns the maximum acceptable width for an output field
+ formatted with the given TYPE. */
+int
+fmt_max_output_width (enum fmt_type type)
+{
+ return fmt_max_width (type, FMT_FOR_OUTPUT);
+}
+
+/* Returns the maximum number of decimal places allowed in an
+ output field of the given TYPE and WIDTH. */
+int
+fmt_max_output_decimals (enum fmt_type type, int width)
+{
+ assert (valid_width (type, width, false));
+ return fmt_max_decimals (type, width, FMT_FOR_OUTPUT);
+}
+
+/* Returns the width step for a field formatted with the given
+ TYPE. Field width must be a multiple of the width step. */
+int
+fmt_step_width (enum fmt_type type)
+{
+ return (fmt_get_category (type) == FMT_CAT_HEXADECIMAL || type == FMT_AHEX
+ ? 2 : 1);
+}
+
+/* Returns true if TYPE is used for string fields,
+ false if it is used for numeric fields. */
+bool
+fmt_is_string (enum fmt_type type)
+{
+ return fmt_get_category (type) == FMT_CAT_STRING;
+}
+
+/* Returns true if TYPE is used for numeric fields,
+ false if it is used for string fields. */
+bool
+fmt_is_numeric (enum fmt_type type)
+{
+ return !fmt_is_string (type);
+}
+
+/* Returns the format TYPE's category.
+ Each format type is in exactly one category,
+ and each category's value is bitwise disjoint from every other
+ category. Thus, the return value may be tested for equality
+ or compared bitwise against a mask of FMT_CAT_* values. */
+enum fmt_category
+fmt_get_category (enum fmt_type type)
+{
+ return get_fmt_desc (type)->category;
+}
+
+/* Returns the output format selected by default when TYPE is
+ used as an input format. */
+enum fmt_type
+fmt_input_to_output (enum fmt_type type)
+{
+ switch (fmt_get_category (type))
+ {
+ case FMT_CAT_STRING:
+ return FMT_A;
+
+ case FMT_CAT_LEGACY:
+ case FMT_CAT_BINARY:
+ case FMT_CAT_HEXADECIMAL:
+ return FMT_F;
+
+ default:
+ return type;
+ }
+}
+
+/* Returns the SPSS format type corresponding to the given PSPP
+ format type. */
+int
+fmt_to_io (enum fmt_type type)
+{
+ return get_fmt_desc (type)->io;
+}
+
+/* Determines the PSPP format corresponding to the given SPSS
+ format type. If successful, sets *FMT_TYPE to the PSPP format
+ and returns true. On failure, return false. */
+bool
+fmt_from_io (int io, enum fmt_type *fmt_type)
+{
+ switch (io)
+ {
+#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
+ case IO: \
+ *fmt_type = FMT_##NAME; \
+ return true;
+#include "format.def"
+ default:
+ return false;
+ }
+}
+
+/* Translate U32, which is in the form found in SAV and SPV files, into a
+ format specification, and stores the new specification in *F.
+
+ If LOOSE is false, checks that the format specification is appropriate as an
+ output format for a variable with the given WIDTH and reports an error if
+ not. If LOOSE is true, instead adjusts the format's width and decimals as
+ necessary to be suitable.
+
+ Return true if successful, false if there was an error.. */
+bool
+fmt_from_u32 (uint32_t u32, int width, bool loose, struct fmt_spec *f)
+{
+ uint8_t raw_type = u32 >> 16;
+ uint8_t w = u32 >> 8;
+ uint8_t d = u32;
+
+ msg_disable ();
+ f->w = w;
+ f->d = d;
+ bool ok = fmt_from_io (raw_type, &f->type);
+ if (ok)
+ {
+ if (loose)
+ fmt_fix_output (f);
+ else
+ ok = fmt_check_output (f);
+ }
+ if (ok)
+ ok = fmt_check_width_compat (f, width);
+ msg_enable ();
+
+ return ok;
+}
+
+/* Returns true if TYPE may be used as an input format,
+ false otherwise. */
+bool
+fmt_usable_for_input (enum fmt_type type)
+{
+ assert (is_fmt_type (type));
+ return fmt_get_category (type) != FMT_CAT_CUSTOM;
+}
+
+/* For time and date formats, returns a template used for input and output in a
+ field of the given WIDTH.
+
+ WIDTH only affects whether a 2-digit year or a 4-digit year is used, that
+ is, whether the returned string contains "yy" or "yyyy", and whether seconds
+ are include, that is, whether the returned string contains ":SS". A caller
+ that doesn't care whether the returned string contains "yy" or "yyyy" or
+ ":SS" can just specify 0 to omit them. */
+const char *
+fmt_date_template (enum fmt_type type, int width)
+{
+ const char *s1, *s2;
+
+ switch (type)
+ {
+ case FMT_DATE:
+ s1 = "dd-mmm-yy";
+ s2 = "dd-mmm-yyyy";
+ break;
+
+ case FMT_ADATE:
+ s1 = "mm/dd/yy";
+ s2 = "mm/dd/yyyy";
+ break;
+
+ case FMT_EDATE:
+ s1 = "dd.mm.yy";
+ s2 = "dd.mm.yyyy";
+ break;
+
+ case FMT_JDATE:
+ s1 = "yyddd";
+ s2 = "yyyyddd";
+ break;
+
+ case FMT_SDATE:
+ s1 = "yy/mm/dd";
+ s2 = "yyyy/mm/dd";
+ break;
+
+ case FMT_QYR:
+ s1 = "q Q yy";
+ s2 = "q Q yyyy";
+ break;
+
+ case FMT_MOYR:
+ s1 = "mmm yy";
+ s2 = "mmm yyyy";
+ break;
+
+ case FMT_WKYR:
+ s1 = "ww WK yy";
+ s2 = "ww WK yyyy";
+ break;
+
+ case FMT_DATETIME:
+ s1 = "dd-mmm-yyyy HH:MM";
+ s2 = "dd-mmm-yyyy HH:MM:SS";
+ break;
+
+ case FMT_YMDHMS:
+ s1 = "yyyy-mm-dd HH:MM";
+ s2 = "yyyy-mm-dd HH:MM:SS";
+ break;
+
+ case FMT_MTIME:
+ s1 = "MM";
+ s2 = "MM:SS";
+ break;
+
+ case FMT_TIME:
+ s1 = "HH:MM";
+ s2 = "HH:MM:SS";
+ break;
+
+ case FMT_DTIME:
+ s1 = "D HH:MM";
+ s2 = "D HH:MM:SS";
+ break;
+
+ default:
+ NOT_REACHED ();
+ }
+
+ return width >= strlen (s2) ? s2 : s1;
+}
+
+/* Returns a string representing the format TYPE for use in a GUI dialog. */
+const char *
+fmt_gui_name (enum fmt_type type)
+{
+ switch (type)
+ {
+ case FMT_F:
+ return _("Numeric");
+
+ case FMT_COMMA:
+ return _("Comma");
+
+ case FMT_DOT:
+ return _("Dot");
+
+ case FMT_E:
+ return _("Scientific");
+
+ case FMT_DATE:
+ case FMT_EDATE:
+ case FMT_SDATE:
+ case FMT_ADATE:
+ case FMT_JDATE:
+ case FMT_QYR:
+ case FMT_MOYR:
+ case FMT_WKYR:
+ case FMT_DATETIME:
+ case FMT_YMDHMS:
+ case FMT_MTIME:
+ case FMT_TIME:
+ case FMT_DTIME:
+ case FMT_WKDAY:
+ case FMT_MONTH:
+ return _("Date");
+
+ case FMT_DOLLAR:
+ return _("Dollar");
+
+ case FMT_CCA:
+ case FMT_CCB:
+ case FMT_CCC:
+ case FMT_CCD:
+ case FMT_CCE:
+ return _("Custom");
+
+ case FMT_A:
+ return _("String");
+
+ default:
+ return fmt_name (type);
+ }
+}
+\f
+/* Returns true if TYPE is a valid format type,
+ false otherwise. */
+bool
+is_fmt_type (enum fmt_type type)
+{
+ return type < FMT_NUMBER_OF_FORMATS;
+}
+
+/* Returns true if WIDTH is a valid width for the given format
+ TYPE, for the given USE. */
+static bool
+valid_width (enum fmt_type type, int width, enum fmt_use use)
+{
+ return (width >= fmt_min_width (type, use)
+ && width <= fmt_max_width (type, use));
+}
+
+/* Returns the maximum number of decimal digits in an unsigned
+ binary number that is BYTES bytes long. */
+static int
+max_digits_for_bytes (int bytes)
+{
+ int map[8] = {3, 5, 8, 10, 13, 15, 17, 20};
+ assert (bytes > 0 && bytes <= sizeof map / sizeof *map);
+ return map[bytes - 1];
+}
+
+/* Clamp FMT's width to the range and values allowed by FMT's type. */
+static void
+fmt_clamp_width (struct fmt_spec *fmt, enum fmt_use use)
+{
+ unsigned int step;
+ int min_w, max_w;
+
+ min_w = fmt_min_width (fmt->type, use);
+ max_w = fmt_max_width (fmt->type, use);
+ if (fmt->w < min_w)
+ fmt->w = min_w;
+ else if (fmt->w > max_w)
+ fmt->w = max_w;
+
+ /* Round width to step. */
+ step = fmt_step_width (fmt->type);
+ fmt->w = ROUND_DOWN (fmt->w, step);
+}
+
+/* Clamp FMT's decimal places to the range allowed by FMT's type and width. */
+static void
+fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
+{
+ int max_d;
+
+ max_d = fmt_max_decimals (fmt->type, fmt->w, use);
+ if (fmt->d < 0)
+ fmt->d = 0;
+ else if (fmt->d > max_d)
+ 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)
+{
+ affix->s = s[0] == '\0' ? CONST_CAST (char *, "") : xstrdup (s);
+ affix->width = u8_strwidth (CHAR_CAST (const uint8_t *, s), "UTF-8");
+}
+
+/* Frees data in AFFIX. */
+static void
+fmt_affix_free (struct fmt_affix *affix)
+{
+ if (affix->s[0])
+ free (affix->s);
+}
+
+static void
+fmt_number_style_init (struct fmt_number_style *style)
+{
+ 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;
+}
+
+static void
+fmt_number_style_clone (struct fmt_number_style *new,
+ const struct fmt_number_style *old)
+{
+ 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;
+}
+
+/* Destroys a struct fmt_number_style. */
+static void
+fmt_number_style_destroy (struct fmt_number_style *style)
+{
+ if (style != NULL)
+ {
+ fmt_affix_free (&style->neg_prefix);
+ fmt_affix_free (&style->prefix);
+ fmt_affix_free (&style->suffix);
+ fmt_affix_free (&style->neg_suffix);
+ }
+}
+
+/* Returns the total width of the standard prefix and suffix for STYLE, in
+ display columns (e.g. as returned by u8_strwidth()). */
+int
+fmt_affix_width (const struct fmt_number_style *style)
+{
+ return style->prefix.width + style->suffix.width;
+}
+
+/* Returns the total width of the negative prefix and suffix for STYLE, in
+ display columns (e.g. as returned by u8_strwidth()). */
+int
+fmt_neg_affix_width (const struct fmt_number_style *style)
+{
+ return style->neg_prefix.width + style->neg_suffix.width;
+}
+
+/* Returns the struct fmt_desc for the given format TYPE. */
+static const struct fmt_desc *
+get_fmt_desc (enum fmt_type type)
+{
+ static const struct fmt_desc formats[FMT_NUMBER_OF_FORMATS] =
+ {
+#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
+ {#NAME, IMIN, OMIN, IO, CATEGORY},
+#include "format.def"
+ };
+
+ assert (is_fmt_type (type));
+ return &formats[type];
+}
+
+const struct fmt_spec F_8_0 = {FMT_F, 8, 0};
+const struct fmt_spec F_8_2 = {FMT_F, 8, 2};
+const struct fmt_spec F_4_3 = {FMT_F, 4, 3};
+const struct fmt_spec F_5_1 = {FMT_F, 5, 1};