format: Count prefix and suffix width in terms of display columns.
[pspp-builds.git] / src / data / format.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "format.h"
20
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <uniwidth.h>
24
25 #include "data/identifier.h"
26 #include "data/settings.h"
27 #include "data/value.h"
28 #include "data/variable.h"
29 #include "libpspp/assertion.h"
30 #include "libpspp/cast.h"
31 #include "libpspp/compiler.h"
32 #include "libpspp/message.h"
33 #include "libpspp/misc.h"
34 #include "libpspp/str.h"
35
36 #include "gl/minmax.h"
37 #include "gl/xalloc.h"
38
39 #include "gettext.h"
40 #define _(msgid) gettext (msgid)
41
42 struct fmt_settings
43   {
44     struct fmt_number_style styles[FMT_NUMBER_OF_FORMATS];
45   };
46
47 bool is_fmt_type (enum fmt_type);
48
49 static bool valid_width (enum fmt_type, int width, bool for_input);
50
51 static int max_digits_for_bytes (int bytes);
52
53 static void fmt_affix_set (struct fmt_affix *, const char *);
54 static void fmt_affix_free (struct fmt_affix *);
55
56 static void fmt_number_style_init (struct fmt_number_style *);
57 static void fmt_number_style_clone (struct fmt_number_style *,
58                                     const struct fmt_number_style *);
59 static void fmt_number_style_destroy (struct fmt_number_style *);
60
61 /* Creates and returns a new struct fmt_settings with default format styles. */
62 struct fmt_settings *
63 fmt_settings_create (void)
64 {
65   struct fmt_settings *settings;
66   int t;
67
68   settings = xzalloc (sizeof *settings);
69   for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
70     fmt_number_style_init (&settings->styles[t]);
71   fmt_settings_set_decimal (settings, '.');
72
73   return settings;
74 }
75
76 /* Destroys SETTINGS. */
77 void
78 fmt_settings_destroy (struct fmt_settings *settings)
79 {
80   if (settings != NULL)
81     {
82       int t;
83
84       for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
85         fmt_number_style_destroy (&settings->styles[t]);
86
87       free (settings->styles);
88     }
89 }
90
91 /* Returns a copy of SETTINGS. */
92 struct fmt_settings *
93 fmt_settings_clone (const struct fmt_settings *old)
94 {
95   struct fmt_settings *new;
96   int t;
97
98   new = xmalloc (sizeof *new);
99   for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
100     fmt_number_style_clone (&new->styles[t], &old->styles[t]);
101
102   return new;
103 }
104
105 /* Returns the number formatting style associated with the given
106    format TYPE. */
107 const struct fmt_number_style *
108 fmt_settings_get_style (const struct fmt_settings *settings,
109                         enum fmt_type type)
110 {
111   assert (is_fmt_type (type));
112   return &settings->styles[type];
113 }
114
115 /* Sets the number style for TYPE to have the given DECIMAL and GROUPING
116    characters, negative prefix NEG_PREFIX, prefix PREFIX, suffix SUFFIX, and
117    negative suffix NEG_SUFFIX.  All of the strings are UTF-8 encoded. */
118 void
119 fmt_settings_set_style (struct fmt_settings *settings, enum fmt_type type,
120                         char decimal, char grouping,
121                         const char *neg_prefix, const char *prefix,
122                         const char *suffix, const char *neg_suffix)
123 {
124   struct fmt_number_style *style = &settings->styles[type];
125   int total_bytes, total_width;
126
127   assert (grouping == '.' || grouping == ',' || grouping == 0);
128   assert (decimal == '.' || decimal == ',');
129   assert (decimal != grouping);
130
131   fmt_number_style_destroy (style);
132
133   fmt_affix_set (&style->neg_prefix, neg_prefix);
134   fmt_affix_set (&style->prefix, prefix);
135   fmt_affix_set (&style->suffix, suffix);
136   fmt_affix_set (&style->neg_suffix, neg_suffix);
137   style->decimal = decimal;
138   style->grouping = grouping;
139
140   total_bytes = (strlen (neg_prefix) + strlen (prefix)
141                  + strlen (suffix) + strlen (neg_suffix));
142   total_width = (style->neg_prefix.width + style->prefix.width
143                  + style->suffix.width + style->neg_suffix.width);
144   style->extra_bytes = MAX (0, total_bytes - total_width);
145 }
146
147 /* Sets the decimal point character for the settings in S to DECIMAL.
148
149    This has no effect on custom currency formats. */
150 void
151 fmt_settings_set_decimal (struct fmt_settings *s, char decimal)
152 {
153   int grouping = decimal == '.' ? ',' : '.';
154   assert (decimal == '.' || decimal == ',');
155
156   fmt_settings_set_style (s, FMT_F,      decimal,        0, "-",  "",  "", "");
157   fmt_settings_set_style (s, FMT_E,      decimal,        0, "-",  "",  "", "");
158   fmt_settings_set_style (s, FMT_COMMA,  decimal, grouping, "-",  "",  "", "");
159   fmt_settings_set_style (s, FMT_DOT,   grouping,  decimal, "-",  "",  "", "");
160   fmt_settings_set_style (s, FMT_DOLLAR, decimal, grouping, "-", "$",  "", "");
161   fmt_settings_set_style (s, FMT_PCT,    decimal,        0, "-",  "", "%", "");
162 }
163
164 /* Returns an input format specification with type TYPE, width W,
165    and D decimals. */
166 struct fmt_spec
167 fmt_for_input (enum fmt_type type, int w, int d)
168 {
169   struct fmt_spec f;
170   f.type = type;
171   f.w = w;
172   f.d = d;
173   assert (fmt_check_input (&f));
174   return f;
175 }
176
177 /* Returns an output format specification with type TYPE, width
178    W, and D decimals. */
179 struct fmt_spec
180 fmt_for_output (enum fmt_type type, int w, int d)
181 {
182   struct fmt_spec f;
183   f.type = type;
184   f.w = w;
185   f.d = d;
186   assert (fmt_check_output (&f));
187   return f;
188 }
189
190 /* Returns the output format specifier corresponding to input
191    format specifier INPUT. */
192 struct fmt_spec
193 fmt_for_output_from_input (const struct fmt_spec *input)
194 {
195   struct fmt_spec output;
196
197   assert (fmt_check_input (input));
198
199   output.type = fmt_input_to_output (input->type);
200   output.w = input->w;
201   if (output.w > fmt_max_output_width (output.type))
202     output.w = fmt_max_output_width (output.type);
203   else if (output.w < fmt_min_output_width (output.type))
204     output.w = fmt_min_output_width (output.type);
205   output.d = input->d;
206
207   switch (input->type)
208     {
209     case FMT_Z:
210       output.w++;
211       if (output.d > 0)
212         output.w++;
213       break;
214
215     case FMT_F:
216     case FMT_COMMA:
217     case FMT_DOT:
218     case FMT_DOLLAR:
219     case FMT_PCT:
220       {
221         const struct fmt_number_style *style =
222           settings_get_style (input->type);
223
224         output.w += fmt_affix_width (style);
225         if (style->grouping != 0 && input->w - input->d >= 3)
226           output.w += (input->w - input->d - 1) / 3;
227         if (output.d > 0)
228           output.w++;
229       }
230       break;
231
232     case FMT_N:
233       if (output.d > 0)
234         output.w++;
235       break;
236
237     case FMT_E:
238       output.d = MAX (input->d, 3);
239       output.w = MAX (input->w, output.d + 7);
240       break;
241
242     case FMT_PIBHEX:
243       output.w = max_digits_for_bytes (input->w / 2) + 1;
244       break;
245
246     case FMT_RB:
247     case FMT_RBHEX:
248       output.w = 8;
249       output.d = 2;
250       break;
251
252     case FMT_P:
253     case FMT_PK:
254       output.w = 2 * input->w + (input->d > 0);
255       break;
256
257     case FMT_IB:
258     case FMT_PIB:
259       output.w = max_digits_for_bytes (input->w) + 1;
260       if (output.d > 0)
261         output.w++;
262       break;
263
264     case FMT_CCA:
265     case FMT_CCB:
266     case FMT_CCC:
267     case FMT_CCD:
268     case FMT_CCE:
269       NOT_REACHED ();
270
271     case FMT_A:
272       break;
273
274     case FMT_AHEX:
275       output.w = input->w / 2;
276       break;
277
278     case FMT_DATE:
279     case FMT_EDATE:
280     case FMT_SDATE:
281     case FMT_ADATE:
282     case FMT_JDATE:
283     case FMT_QYR:
284     case FMT_MOYR:
285     case FMT_WKYR:
286     case FMT_TIME:
287     case FMT_DTIME:
288     case FMT_DATETIME:
289     case FMT_WKDAY:
290     case FMT_MONTH:
291       break;
292
293     default:
294       NOT_REACHED ();
295     }
296
297   if (output.w > fmt_max_output_width (output.type))
298     output.w = fmt_max_output_width (output.type);
299
300   assert (fmt_check_output (&output));
301   return output;
302 }
303
304 /* Returns the default format for the given WIDTH: F8.2 format
305    for a numeric value, A format for a string value. */
306 struct fmt_spec
307 fmt_default_for_width (int width)
308 {
309   return (width == 0
310           ? fmt_for_output (FMT_F, 8, 2)
311           : fmt_for_output (FMT_A, width, 0));
312 }
313
314 /* Checks whether SPEC is valid as an input format (if FOR_INPUT)
315    or an output format (otherwise) and returns nonzero if so.
316    Otherwise, emits an error message and returns zero. */
317 bool
318 fmt_check (const struct fmt_spec *spec, bool for_input)
319 {
320   const char *io_fmt = for_input ? _("Input format") : _("Output format");
321   char str[FMT_STRING_LEN_MAX + 1];
322   int min_w, max_w, max_d;
323
324   assert (is_fmt_type (spec->type));
325   fmt_to_string (spec, str);
326
327   if (for_input && !fmt_usable_for_input (spec->type))
328     {
329       msg (SE, _("Format %s may not be used for input."), str);
330       return false;
331     }
332
333   if (spec->w % fmt_step_width (spec->type))
334     {
335       assert (fmt_step_width (spec->type) == 2);
336       msg (SE, _("%s specifies width %d, but %s requires an even width."),
337            str, spec->w, fmt_name (spec->type));
338       return false;
339     }
340
341   min_w = fmt_min_width (spec->type, for_input);
342   max_w = fmt_max_width (spec->type, for_input);
343   if (spec->w < min_w || spec->w > max_w)
344     {
345       msg (SE, _("%s %s specifies width %d, but "
346                  "%s requires a width between %d and %d."),
347            io_fmt, str, spec->w, fmt_name (spec->type), min_w, max_w);
348       return false;
349     }
350
351   max_d = fmt_max_decimals (spec->type, spec->w, for_input);
352   if (!fmt_takes_decimals (spec->type) && spec->d != 0)
353     {
354       msg (SE, ngettext ("%s %s specifies %d decimal place, but "
355                          "%s does not allow any decimals.",
356                          "%s %s specifies %d decimal places, but "
357                          "%s does not allow any decimals.",
358                          spec->d),
359            io_fmt, str, spec->d, fmt_name (spec->type));
360       return false;
361     }
362   else if (spec->d > max_d)
363     {
364       if (max_d > 0)
365         msg (SE, ngettext ("%s %s specifies %d decimal place, but "
366                            "the given width allows at most %d decimals.",
367                            "%s %s specifies %d decimal places, but "
368                            "the given width allows at most %d decimals.",
369                            spec->d),
370              io_fmt, str, spec->d, max_d);
371       else
372         msg (SE, ngettext ("%s %s specifies %d decimal place, but "
373                            "the given width does not allow for any decimals.",
374                            "%s %s specifies %d decimal places, but "
375                            "the given width does not allow for any decimals.",
376                            spec->d),
377              io_fmt, str, spec->d);
378       return false;
379     }
380
381   return true;
382 }
383
384 /* Checks whether SPEC is valid as an input format and returns
385    nonzero if so.  Otherwise, emits an error message and returns
386    zero. */
387 bool
388 fmt_check_input (const struct fmt_spec *spec)
389 {
390   return fmt_check (spec, true);
391 }
392
393 /* Checks whether SPEC is valid as an output format and returns
394    true if so.  Otherwise, emits an error message and returns false. */
395 bool
396 fmt_check_output (const struct fmt_spec *spec)
397 {
398   return fmt_check (spec, false);
399 }
400
401 /* Checks that FORMAT is appropriate for a variable of the given
402    VAR_TYPE and returns true if so.  Otherwise returns false and
403    emits an error message. */
404 bool
405 fmt_check_type_compat (const struct fmt_spec *format, enum val_type var_type)
406 {
407   assert (val_type_is_valid (var_type));
408   if ((var_type == VAL_STRING) != (fmt_is_string (format->type) != 0))
409     {
410       char str[FMT_STRING_LEN_MAX + 1];
411       msg (SE, _("%s variables are not compatible with %s format %s."),
412            var_type == VAL_STRING ? _("String") : _("Numeric"),
413            var_type == VAL_STRING ? _("numeric") : _("string"),
414            fmt_to_string (format, str));
415       return false;
416     }
417   return true;
418 }
419
420 /* Checks that FORMAT is appropriate for a variable of the given
421    WIDTH and returns true if so.  Otherwise returns false and
422    emits an error message. */
423 bool
424 fmt_check_width_compat (const struct fmt_spec *format, int width)
425 {
426   if (!fmt_check_type_compat (format, val_type_from_width (width)))
427     return false;
428   if (fmt_var_width (format) != width)
429     {
430       char str[FMT_STRING_LEN_MAX + 1];
431       msg (SE, _("String variable with width %d is not compatible with "
432                  "format %s."),
433            width, fmt_to_string (format, str));
434       return false;
435     }
436   return true;
437 }
438
439 /* Returns the width corresponding to FORMAT.  The return value
440    is the width of the `union value's required by FORMAT. */
441 int
442 fmt_var_width (const struct fmt_spec *format)
443 {
444   return (format->type == FMT_AHEX ? format->w / 2
445           : format->type == FMT_A ? format->w
446           : 0);
447 }
448
449 /* Converts F to its string representation (for instance, "F8.2")
450    in BUFFER.  Returns BUFFER.
451
452    If F has decimals, they are included in the output string,
453    even if F's format type does not allow decimals, to allow
454    accurately presenting incorrect formats to the user. */
455 char *
456 fmt_to_string (const struct fmt_spec *f, char buffer[FMT_STRING_LEN_MAX + 1])
457 {
458   if (fmt_takes_decimals (f->type) || f->d > 0)
459     snprintf (buffer, FMT_STRING_LEN_MAX + 1,
460               "%s%d.%d", fmt_name (f->type), f->w, f->d);
461   else
462     snprintf (buffer, FMT_STRING_LEN_MAX + 1,
463               "%s%d", fmt_name (f->type), f->w);
464   return buffer;
465 }
466
467 /* Returns true if A and B are identical formats,
468    false otherwise. */
469 bool
470 fmt_equal (const struct fmt_spec *a, const struct fmt_spec *b)
471 {
472   return a->type == b->type && a->w == b->w && a->d == b->d;
473 }
474
475 /* Adjusts FMT to be valid for a value of the given WIDTH. */
476 void
477 fmt_resize (struct fmt_spec *fmt, int width)
478 {
479   if ((width > 0) != fmt_is_string (fmt->type))
480     {
481       /* Changed from numeric to string or vice versa.  Set to
482          default format for new width. */
483       *fmt = fmt_default_for_width (width);
484     }
485   else if (width > 0)
486     {
487       /* Changed width of string.  Preserve format type, adjust
488          width. */
489       fmt->w = fmt->type == FMT_AHEX ? width * 2 : width;
490     }
491   else
492     {
493       /* Still numeric. */
494     }
495 }
496
497 /* Adjusts FMT's width and decimal places to be valid for an
498    input format (if FOR_INPUT) or an output format (if
499    !FOR_INPUT).  */
500 void
501 fmt_fix (struct fmt_spec *fmt, bool for_input)
502 {
503   int min_w, max_w;
504   int max_d;
505
506   /* Clamp width to those allowed by format. */
507   min_w = fmt_min_width (fmt->type, for_input);
508   max_w = fmt_max_width (fmt->type, for_input);
509   if (fmt->w < min_w)
510     fmt->w = min_w;
511   else if (fmt->w > max_w)
512     fmt->w = max_w;
513
514   /* First, if FMT has more decimal places than allowed, attempt
515      to increase FMT's width until that number of decimal places
516      can be achieved. */
517   if (fmt->d > fmt_max_decimals (fmt->type, fmt->w, for_input))
518     {
519       int w;
520       for (w = fmt->w; w <= max_w; w++)
521         if (fmt_max_decimals (fmt->type, w, for_input) >= fmt->d)
522           {
523             fmt->w = w;
524             break;
525           }
526     }
527
528   /* Clamp decimals to those allowed by format and width. */
529   max_d = fmt_max_decimals (fmt->type, fmt->w, for_input);
530   if (fmt->d < 0)
531     fmt->d = 0;
532   else if (fmt->d > max_d)
533     fmt->d = max_d;
534 }
535
536 /* Adjusts FMT's width and decimal places to be valid for an
537    input format.  */
538 void
539 fmt_fix_input (struct fmt_spec *fmt)
540 {
541   fmt_fix (fmt, true);
542 }
543
544 /* Adjusts FMT's width and decimal places to be valid for an
545    output format.  */
546 void
547 fmt_fix_output (struct fmt_spec *fmt)
548 {
549   fmt_fix (fmt, false);
550 }
551 \f
552 /* Describes a display format. */
553 struct fmt_desc
554   {
555     char name[9];
556     int min_input_width, min_output_width;
557     int io;
558     enum fmt_category category;
559   };
560
561 static const struct fmt_desc *get_fmt_desc (enum fmt_type type);
562
563 /* Returns the name of the given format TYPE. */
564 const char *
565 fmt_name (enum fmt_type type)
566 {
567   return get_fmt_desc (type)->name;
568 }
569
570 /* Tries to parse NAME as a format type.
571    If successful, stores the type in *TYPE and returns true.
572    On failure, returns false. */
573 bool
574 fmt_from_name (const char *name, enum fmt_type *type)
575 {
576   int i;
577
578   for (i = 0; i < FMT_NUMBER_OF_FORMATS; i++)
579     if (!strcasecmp (name, get_fmt_desc (i)->name))
580       {
581         *type = i;
582         return true;
583       }
584   return false;
585 }
586
587 /* Returns true if TYPE accepts decimal places,
588    false otherwise. */
589 bool
590 fmt_takes_decimals (enum fmt_type type)
591 {
592   return fmt_max_output_decimals (type, fmt_max_output_width (type)) > 0;
593 }
594
595 /* Returns the minimum width of the given format TYPE,
596    for input if FOR_INPUT is true,
597    for output otherwise. */
598 int
599 fmt_min_width (enum fmt_type type, bool for_input)
600 {
601   return for_input ? fmt_min_input_width (type) : fmt_min_output_width (type);
602 }
603
604 /* Returns the maximum width of the given format TYPE,
605    for input if FOR_INPUT is true,
606    for output otherwise. */
607 int
608 fmt_max_width (enum fmt_type type, bool for_input UNUSED)
609 {
610   /* Maximum width is actually invariant of whether the format is
611      for input or output, so FOR_INPUT is unused. */
612   assert (is_fmt_type (type));
613   switch (type)
614     {
615     case FMT_P:
616     case FMT_PK:
617     case FMT_PIBHEX:
618     case FMT_RBHEX:
619       return 16;
620
621     case FMT_IB:
622     case FMT_PIB:
623     case FMT_RB:
624       return 8;
625
626     case FMT_A:
627       return MAX_STRING;
628
629     case FMT_AHEX:
630       return 2 * MAX_STRING;
631
632     default:
633       return 40;
634     }
635 }
636
637 /* Returns the maximum number of decimal places allowed for the
638    given format TYPE with a width of WIDTH places,
639    for input if FOR_INPUT is true,
640    for output otherwise. */
641 int
642 fmt_max_decimals (enum fmt_type type, int width, bool for_input)
643 {
644   int max_d;
645
646   switch (type)
647     {
648     case FMT_F:
649     case FMT_COMMA:
650     case FMT_DOT:
651       max_d = for_input ? width : width - 1;
652       break;
653
654     case FMT_DOLLAR:
655     case FMT_PCT:
656       max_d = for_input ? width : width - 2;
657       break;
658
659     case FMT_E:
660       max_d = for_input ? width : width - 7;
661       break;
662
663     case FMT_CCA:
664     case FMT_CCB:
665     case FMT_CCC:
666     case FMT_CCD:
667     case FMT_CCE:
668       assert (!for_input);
669       max_d = width - 1;
670       break;
671
672     case FMT_N:
673     case FMT_Z:
674       max_d = width;
675       break;
676
677     case FMT_P:
678       max_d = width * 2 - 1;
679       break;
680
681     case FMT_PK:
682       max_d = width * 2;
683       break;
684
685     case FMT_IB:
686     case FMT_PIB:
687       max_d = max_digits_for_bytes (width);
688       break;
689
690     case FMT_PIBHEX:
691       max_d = 0;
692       break;
693
694     case FMT_RB:
695     case FMT_RBHEX:
696       max_d = 16;
697       break;
698
699     case FMT_DATE:
700     case FMT_ADATE:
701     case FMT_EDATE:
702     case FMT_JDATE:
703     case FMT_SDATE:
704     case FMT_QYR:
705     case FMT_MOYR:
706     case FMT_WKYR:
707       max_d = 0;
708       break;
709
710     case FMT_DATETIME:
711       max_d = width - 21;
712       break;
713
714     case FMT_TIME:
715       max_d = width - 9;
716       break;
717
718     case FMT_DTIME:
719       max_d = width - 12;
720       break;
721
722     case FMT_WKDAY:
723     case FMT_MONTH:
724     case FMT_A:
725     case FMT_AHEX:
726       max_d = 0;
727       break;
728
729     default:
730       NOT_REACHED ();
731     }
732
733   if (max_d < 0)
734     max_d = 0;
735   else if (max_d > 16)
736     max_d = 16;
737   return max_d;
738 }
739
740 /* Returns the minimum acceptable width for an input field
741    formatted with the given TYPE. */
742 int
743 fmt_min_input_width (enum fmt_type type)
744 {
745   return get_fmt_desc (type)->min_input_width;
746 }
747
748 /* Returns the maximum acceptable width for an input field
749    formatted with the given TYPE. */
750 int
751 fmt_max_input_width (enum fmt_type type)
752 {
753   return fmt_max_width (type, true);
754 }
755
756 /* Returns the maximum number of decimal places allowed in an
757    input field of the given TYPE and WIDTH. */
758 int
759 fmt_max_input_decimals (enum fmt_type type, int width)
760 {
761   assert (valid_width (type, width, true));
762   return fmt_max_decimals (type, width, true);
763 }
764
765 /* Returns the minimum acceptable width for an output field
766    formatted with the given TYPE. */
767 int
768 fmt_min_output_width (enum fmt_type type)
769 {
770   return get_fmt_desc (type)->min_output_width;
771 }
772
773 /* Returns the maximum acceptable width for an output field
774    formatted with the given TYPE. */
775 int
776 fmt_max_output_width (enum fmt_type type)
777 {
778   return fmt_max_width (type, false);
779 }
780
781 /* Returns the maximum number of decimal places allowed in an
782    output field of the given TYPE and WIDTH. */
783 int
784 fmt_max_output_decimals (enum fmt_type type, int width)
785 {
786   assert (valid_width (type, width, false));
787   return fmt_max_decimals (type, width, false);
788 }
789
790 /* Returns the width step for a field formatted with the given
791    TYPE.  Field width must be a multiple of the width step. */
792 int
793 fmt_step_width (enum fmt_type type)
794 {
795   return (fmt_get_category (type) == FMT_CAT_HEXADECIMAL || type == FMT_AHEX
796           ? 2 : 1);
797 }
798
799 /* Returns true if TYPE is used for string fields,
800    false if it is used for numeric fields. */
801 bool
802 fmt_is_string (enum fmt_type type)
803 {
804   return fmt_get_category (type) == FMT_CAT_STRING;
805 }
806
807 /* Returns true if TYPE is used for numeric fields,
808    false if it is used for string fields. */
809 bool
810 fmt_is_numeric (enum fmt_type type)
811 {
812   return !fmt_is_string (type);
813 }
814
815 /* Returns the format TYPE's category.
816    Each format type is in exactly one category,
817    and each category's value is bitwise disjoint from every other
818    category.  Thus, the return value may be tested for equality
819    or compared bitwise against a mask of FMT_CAT_* values. */
820 enum fmt_category
821 fmt_get_category (enum fmt_type type)
822 {
823   return get_fmt_desc (type)->category;
824 }
825
826 /* Returns the output format selected by default when TYPE is
827    used as an input format. */
828 enum fmt_type
829 fmt_input_to_output (enum fmt_type type)
830 {
831   switch (fmt_get_category (type))
832     {
833     case FMT_CAT_STRING:
834       return FMT_A;
835
836     case FMT_CAT_LEGACY:
837     case FMT_CAT_BINARY:
838     case FMT_CAT_HEXADECIMAL:
839       return FMT_F;
840
841     default:
842       return type;
843     }
844 }
845
846 /* Returns the SPSS format type corresponding to the given PSPP
847    format type. */
848 int
849 fmt_to_io (enum fmt_type type)
850 {
851   return get_fmt_desc (type)->io;
852 }
853
854 /* Determines the PSPP format corresponding to the given SPSS
855    format type.  If successful, sets *FMT_TYPE to the PSPP format
856    and returns true.  On failure, return false. */
857 bool
858 fmt_from_io (int io, enum fmt_type *fmt_type)
859 {
860   switch (io)
861     {
862 #define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY)     \
863     case IO:                                          \
864       *fmt_type = FMT_##NAME;                           \
865       return true;
866 #include "format.def"
867     default:
868       return false;
869     }
870 }
871
872 /* Returns true if TYPE may be used as an input format,
873    false otherwise. */
874 bool
875 fmt_usable_for_input (enum fmt_type type)
876 {
877   assert (is_fmt_type (type));
878   return fmt_get_category (type) != FMT_CAT_CUSTOM;
879 }
880
881 /* For time and date formats, returns a template used for input
882    and output. */
883 const char *
884 fmt_date_template (enum fmt_type type)
885 {
886   switch (type)
887     {
888     case FMT_DATE:
889       return "dd-mmm-yy";
890     case FMT_ADATE:
891       return "mm/dd/yy";
892     case FMT_EDATE:
893       return "dd.mm.yy";
894     case FMT_JDATE:
895       return "yyddd";
896     case FMT_SDATE:
897       return "yy/mm/dd";
898     case FMT_QYR:
899       return "q Q yy";
900     case FMT_MOYR:
901       return "mmmXyy";
902     case FMT_WKYR:
903       return "ww WK yy";
904     case FMT_DATETIME:
905       return "dd-mmm-yyyy HH:MM";
906     case FMT_TIME:
907       return "H:MM";
908     case FMT_DTIME:
909       return "D HH:MM";
910     default:
911       NOT_REACHED ();
912     }
913 }
914
915 \f
916 /* Returns true if TYPE is a valid format type,
917    false otherwise. */
918 bool
919 is_fmt_type (enum fmt_type type)
920 {
921   return type < FMT_NUMBER_OF_FORMATS;
922 }
923
924 /* Returns true if WIDTH is a valid width for the given format
925    TYPE,
926    for input if FOR_INPUT is true,
927    for output otherwise. */
928 static bool
929 valid_width (enum fmt_type type, int width, bool for_input)
930 {
931   return (width >= fmt_min_width (type, for_input)
932           && width <= fmt_max_width (type, for_input));
933 }
934
935 /* Returns the maximum number of decimal digits in an unsigned
936    binary number that is BYTES bytes long. */
937 static int
938 max_digits_for_bytes (int bytes)
939 {
940   int map[8] = {3, 5, 8, 10, 13, 15, 17, 20};
941   assert (bytes > 0 && bytes <= sizeof map / sizeof *map);
942   return map[bytes - 1];
943 }
944 \f
945 /* Sets AFFIX's string value to S, a UTF-8 encoded string. */
946 static void
947 fmt_affix_set (struct fmt_affix *affix, const char *s)
948 {
949   affix->s = s[0] == '\0' ? CONST_CAST (char *, "") : xstrdup (s);
950   affix->width = u8_strwidth (CHAR_CAST (const uint8_t *, s), "UTF-8");
951 }
952
953 /* Frees data in AFFIX. */
954 static void
955 fmt_affix_free (struct fmt_affix *affix)
956 {
957   if (affix->s[0])
958     free (affix->s);
959 }
960
961 static void
962 fmt_number_style_init (struct fmt_number_style *style)
963 {
964   fmt_affix_set (&style->neg_prefix, "");
965   fmt_affix_set (&style->prefix, "");
966   fmt_affix_set (&style->suffix, "");
967   fmt_affix_set (&style->neg_suffix, "");
968   style->decimal = '.';
969   style->grouping = 0;
970 }
971
972 static void
973 fmt_number_style_clone (struct fmt_number_style *new,
974                         const struct fmt_number_style *old)
975 {
976   fmt_affix_set (&new->neg_prefix, old->neg_prefix.s);
977   fmt_affix_set (&new->prefix, old->prefix.s);
978   fmt_affix_set (&new->suffix, old->suffix.s);
979   fmt_affix_set (&new->neg_suffix, old->neg_suffix.s);
980   new->decimal = old->decimal;
981   new->grouping = old->grouping;
982   new->extra_bytes = old->extra_bytes;
983 }
984
985 /* Destroys a struct fmt_number_style. */
986 static void
987 fmt_number_style_destroy (struct fmt_number_style *style)
988 {
989   if (style != NULL)
990     {
991       fmt_affix_free (&style->neg_prefix);
992       fmt_affix_free (&style->prefix);
993       fmt_affix_free (&style->suffix);
994       fmt_affix_free (&style->neg_suffix);
995     }
996 }
997
998 /* Returns the total width of the standard prefix and suffix for STYLE, in
999    display columns (e.g. as returned by u8_strwidth()). */
1000 int
1001 fmt_affix_width (const struct fmt_number_style *style)
1002 {
1003   return style->prefix.width + style->suffix.width;
1004 }
1005
1006 /* Returns the total width of the negative prefix and suffix for STYLE, in
1007    display columns (e.g. as returned by u8_strwidth()). */
1008 int
1009 fmt_neg_affix_width (const struct fmt_number_style *style)
1010 {
1011   return style->neg_prefix.width + style->neg_suffix.width;
1012 }
1013
1014 /* Returns the struct fmt_desc for the given format TYPE. */
1015 static const struct fmt_desc *
1016 get_fmt_desc (enum fmt_type type)
1017 {
1018   static const struct fmt_desc formats[FMT_NUMBER_OF_FORMATS] =
1019     {
1020 #define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
1021         {#NAME, IMIN, OMIN, IO, CATEGORY},
1022 #include "format.def"
1023     };
1024
1025   assert (is_fmt_type (type));
1026   return &formats[type];
1027 }
1028
1029 const struct fmt_spec F_8_0 = {FMT_F, 8, 0};