1 /*******************************************************************************
2  * Copyright (c) 2013-2021, Andrés Martinelli <andmarti@gmail.com>             *
3  * All rights reserved.                                                        *
4  *                                                                             *
5  * This file is a part of SC-IM                                                *
6  *                                                                             *
7  * SC-IM is a spreadsheet program that is based on SC. The original authors    *
8  * of SC are James Gosling and Mark Weiser, and mods were later added by       *
9  * Chuck Martin.                                                               *
10  *                                                                             *
11  * Redistribution and use in source and binary forms, with or without          *
12  * modification, are permitted provided that the following conditions are met: *
13  * 1. Redistributions of source code must retain the above copyright           *
14  *    notice, this list of conditions and the following disclaimer.            *
15  * 2. Redistributions in binary form must reproduce the above copyright        *
16  *    notice, this list of conditions and the following disclaimer in the      *
17  *    documentation and/or other materials provided with the distribution.     *
18  * 3. All advertising materials mentioning features or use of this software    *
19  *    must display the following acknowledgement:                              *
20  *    This product includes software developed by Andrés Martinelli            *
21  *    <andmarti@gmail.com>.                                                    *
22  * 4. Neither the name of the Andrés Martinelli nor the                        *
23  *   names of other contributors may be used to endorse or promote products    *
24  *   derived from this software without specific prior written permission.     *
25  *                                                                             *
26  * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY            *
27  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   *
28  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE      *
29  * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY           *
30  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  *
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
33  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  *
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE       *
35  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
36  *******************************************************************************/
37 
38 /**
39  * \file format.c
40  * \author Andrés Martinelli <andmarti@gmail.com>
41  * \date 2017-07-18
42  * \brief TODO Write a tbrief file description.
43  */
44 
45 /*
46  * Based on code of Mark Nagel <nagel@ics.uci.edu>, 20 July 1989
47  *
48  * int
49  * format(fmt, precision, num, buf, buflen)
50  * char *fmt;
51  * double num;
52  * char buf[];
53  * int buflen;
54  *
55  * The format function will produce a string representation of a number
56  * given a _format_ (described below) and a double value.  The result is
57  * written into the passed buffer -- if the resulting string is too
58  * long to fit into the passed buffer, the function returns false.
59  * Otherwise the function returns true.
60  *
61  * The fmt parameter contains the format to use to convert the number.
62  *
63  *  #    Digit placeholder.  If the number has fewer digits on either
64  *      side of the decimal point than  there are '#' characters in
65  *      the format, the extra '#' characters are ignored.  The number
66  *      is rounded to the number of digit placeholders as there are
67  *      to the right of the decimal point.  If there are more digits
68  *      in the number than there are digit placeholders on the left
69  *      side of the decimal point, then those digits are displayed.
70  *
71  *  0    Digit placeholder.  Same as for '#' except that the number
72  *      is padded with zeroes on either side of the decimal point.
73  *      The number of zeroes used in padding is determined by the
74  *      number of digit placeholders after the '0' for digits on
75  *      the left side of the decimal point and by the number of
76  *      digit placeholders before the '0' for digits on the right
77  *      side of the decimal point.
78  *
79  *  .    Decimal point.  Determines how many digits are placed on
80  *      the right and left sides of the decimal point in the number.
81  *      Note that numbers smaller than 1 will begin with a decimal
82  *      point if the left side of the decimal point contains only
83  *      a '#' digit placeholder.  Use a '0' placeholder to get a
84  *      leading zero in decimal formats.
85  *
86  *  %    Percentage.  For each '%' character in the format, the actual
87  *      number gets multiplied by 100 (only for purposes of formatting
88  *      -- the original number is left unmodified) and the '%' character
89  *      is placed in the same position as it is in the format.
90  *
91  *  ,    Thousands separator.  The presence of a ',' in the format
92  *      (multiple commas are treated as one) will cause the number
93  *      to be formatted with a ',' separating each set of three digits
94  *      in the integer part of the number with numbering beginning
95  *      from the right end of the integer.
96  *
97  *  &   Precision.  When this character is present in the fractional
98  *      part of the number, it is equavalent to a number of 0's equal
99  *      to the precision specified in the column format command.  For
100  *      example, if the precision is 3, "&" is equivalent to "000".
101  *
102  *  \    Quote.  This character causes the next character to be
103  *      inserted into the formatted string directly with no
104  *      special interpretation.
105  *
106  *  E- E+ e- e+
107  *    Scientific format.  Causes the number to formatted in scientific
108  *    notation.  The case of the 'E' or 'e' given is preserved.  If
109  *      the format uses a '+', then the sign is always given for the
110  *    exponent value.  If the format uses a '-', then the sign is
111  *    only given when the exponent value is negative.  Note that if
112  *    there is no digit placeholder following the '+' or '-', then
113  *    that part of the formatted number is left out.  In general,
114  *    there should be one or more digit placeholders after the '+'
115  *    or '-'.
116  *
117  *  ;    Format selector.  Use this character to separate the format
118  *    into two distinct formats.  The format to the left of the
119  *    ';' character will be used if the number given is zero or
120  *    positive.  The format to the right of the ';' character is
121  *      used if the number given is negative.
122  *
123  *  Any
124  *    Self insert.  Any other character will be inserted directly
125  *    into the formatted number with no change made to the actual
126  *      number.
127  */
128 
129 #include <stdio.h>
130 #include <string.h>
131 #include <sys/types.h>
132 #include <time.h>
133 #include "sc.h"
134 
135 #define true     1
136 #define false    0
137 #define EOS      '\0'
138 #define MAXBUF   256
139 
140 static char *fmt_int(char *val, char *fmt, int comma, int negative);
141 static char *fmt_frac(char *val, char *fmt, int lprecision);
142 static char *fmt_exp(int val, char *fmt);
143 static void reverse(register char *buf);
144 char * colformat[COLFORMATS];
145 
146 /**
147  * \brief TODO Document format()
148  *
149  * \param[in] fmt
150  * \param[in] lprecision
151  * \param[in] val
152  * \param[in] buf
153  * \param[in] buflen
154  *
155  * returns: ret
156  */
157 
format(char * fmt,int lprecision,double val,char * buf,int buflen)158 int format(char *fmt, int lprecision, double val, char *buf, int buflen) {
159     register char *cp;
160     char *tmp, *tp;
161     int comma = false, negative = false;
162     char *integer = NULL, *decimal = NULL;
163     char *exponent = NULL;
164     int exp_val = 0;
165     int width;
166     char prtfmt[32];
167     static char * mantissa = NULL;
168     static char * tmpfmt1 = NULL, * tmpfmt2 = NULL, * exptmp = NULL;
169     static unsigned mantlen = 0, fmtlen = 0;
170     char * fraction = NULL;
171     int zero_pad = 0;
172 
173     if (fmt == NULL)
174         return(true);
175 
176     if (strlen(fmt) + 1 > fmtlen) {
177         fmtlen = strlen(fmt) + 40;
178         tmpfmt1 = scxrealloc(tmpfmt1, fmtlen);
179         tmpfmt2 = scxrealloc(tmpfmt2, fmtlen);
180         exptmp = scxrealloc(exptmp, fmtlen);
181     }
182     fmt = strcpy(tmpfmt1, fmt);
183     if (buflen + 1 > mantlen) {
184         mantlen = buflen + 40;
185         mantissa = scxrealloc(mantissa, mantlen);
186     }
187 
188 /*
189  * select positive or negative format if necessary
190  */
191     for (cp = fmt; *cp != ';' && *cp != EOS; cp++) {
192     if (*cp == '\\')
193         cp++;
194     }
195     if (*cp == ';') {
196         if (val < 0.0) {
197             val = -val;     /* format should provide sign if desired */
198             fmt = cp + 1;
199         } else
200             *cp = EOS;
201     }
202 
203 /*
204  * extract other information from format and produce a
205  * format string stored in tmpfmt2 also scxmalloc()'d above
206  */
207     tmp = tmpfmt2;
208     for (cp = fmt, tp = tmp; *cp != EOS; cp++) {
209     switch (*cp) {
210         case '\\':
211             *tp++ = *cp++;
212             *tp++ = *cp;
213             break;
214 
215         case ',':
216             comma = true;
217             break;
218 
219         case '.':
220             if (decimal == NULL)
221                 decimal = tp;
222             *tp++ = *cp;
223             break;
224 
225         case '%':
226             val *= 100.0;
227             *tp++ = *cp;
228             break;
229 
230         default:
231             *tp++ = *cp;
232             break;
233     }
234     }
235     *tp = EOS;
236     fmt = tmpfmt2;
237 
238 /*
239  * The following line was necessary due to problems with the gcc
240  * compiler and val being a negative zero.  Thanks to Mike Novack for
241  * the suggestion. - CRM
242  */
243     val = (val + 1.0) - 1.0;
244     if (val < 0.0) {
245         negative = true;
246         val = -val;
247     }
248 /*
249  * extract the exponent from the format if present
250  */
251     for (cp = fmt; *cp != EOS; cp++) {
252         if (*cp == '\\')
253             cp++;
254         else if (*cp == 'e' || *cp == 'E') {
255             if (cp[1] == '+' || cp[1] == '-') {
256                 exponent = strcpy(exptmp, cp);
257                 *cp = EOS;
258                 if (val != 0.0) {
259                     while (val < 1.0) {
260                         val *= 10.0;
261                         exp_val--;
262                     }
263                     while (val >= 10.0) {
264                         val /= 10.0;
265                         exp_val++;
266                     }
267                 }
268             }
269             break;
270         }
271     }
272 
273 /*
274  * determine maximum decimal places and use sprintf
275  * to build initial character form of formatted value.
276  */
277     width = 0;
278     if (decimal) {
279         *decimal++ = EOS;
280         for (cp = decimal; *cp != EOS; cp++) {
281             switch (*cp) {
282             case '\\':
283                 cp++;
284                 break;
285 
286             case '#':
287                 width++;
288                 break;
289 
290             case '0':
291                 zero_pad = ++width;
292                 break;
293 
294             case '&':
295                 width += lprecision;
296                 zero_pad = width;
297                 break;
298             }
299         }
300         zero_pad = strlen(decimal) - zero_pad;
301     }
302     (void) sprintf(prtfmt, "%%.%dlf", width);
303     (void) sprintf(mantissa, prtfmt, val);
304     for (cp = integer = mantissa; *cp != dpoint && *cp != EOS; cp++) {
305         if (*integer == '0')
306             integer++;
307     }
308     if (*cp == dpoint) {
309         fraction = cp + 1;
310         *cp = EOS;
311         cp = fraction + strlen(fraction) - 1;
312         for (; zero_pad > 0 && *cp != EOS; zero_pad--, cp--) { // scim
313         //for (; zero_pad > 0; zero_pad--, cp--) { // sc
314             if (*cp == '0')
315                 *cp = EOS;
316             else
317                 break;
318         }
319     } else
320         fraction = "";
321 
322 /*
323  * format the puppy
324  */
325     {
326     static char * citmp = NULL, * cftmp = NULL;
327     static unsigned cilen = 0, cflen = 0;
328     char * ci, * cf, * ce;
329     int len_ci, len_cf, len_ce;
330     int ret = false;
331 
332     ci = fmt_int(integer, fmt, comma, negative);
333     len_ci = strlen(ci);
334     if (len_ci >= cilen) {
335         cilen = len_ci + 40;
336         citmp = scxrealloc(citmp, cilen);
337     }
338     ci = strcpy(citmp, ci);
339 
340     cf = decimal ? fmt_frac(fraction, decimal, lprecision) : "";
341     len_cf = strlen(cf);
342     if (len_cf >= cflen) {
343         cflen = len_cf + 40;
344         cftmp = scxrealloc(cftmp, cflen); // scim
345         //cftmp = scxrealloc(cftmp, cilen); // sc
346     }
347     cf = strcpy(cftmp, cf);
348 
349     ce = (exponent) ? fmt_exp(exp_val, exponent) : "";
350     len_ce = strlen(ce);
351 /*
352  * Skip copy assuming sprintf doesn't call our format functions
353  *   ce = strcpy(scxmalloc((unsigned)((len_ce = strlen(ce)) + 1)), ce);
354  */
355     if (len_ci + len_cf + len_ce < buflen) {
356         (void) sprintf(buf, "%s%s%s", ci, cf, ce);
357         ret = true;
358     }
359 
360     return (ret);
361     }
362 }
363 
364 /**
365  * \brief TODO Document fmt_int()
366  *
367  * \param[in] val integer part of the value to be formatted
368  * \param[in] fmt integer part of the format
369  * \param[in] comma true if we should comma-ify the value
370  * \param[in] negative true if the value is actually negative
371  * \return none
372  */
373 
fmt_int(char * val,char * fmt,int comma,int negative)374 static char * fmt_int(char *val, char *fmt, int comma, int negative) {
375 
376     int digit, f, v;
377     int thousands = 0;
378     char * cp;
379     static char buf[MAXBUF];
380     char * bufptr = buf;
381 
382 /*
383  * locate the leftmost digit placeholder
384  */
385     for (cp = fmt; *cp != EOS; cp++) {
386         if (*cp == '\\')
387             cp++;
388         else if (*cp == '#' || *cp == '0')
389             break;
390     }
391     digit = (*cp == EOS) ? -1 : cp - fmt;
392 
393 /*
394  * format the value
395  */
396     f = strlen(fmt) - 1;
397     v = (digit >= 0) ? strlen(val) - 1 : -1;
398     while (f >= 0 || v >= 0) {
399         if (f > 0 && fmt[f-1] == '\\') {
400             *bufptr++ = fmt[f--];
401         } else if (f >= 0 && (fmt[f] == '#' || fmt[f] == '0')) {
402             if (v >= 0 || fmt[f] == '0') {
403                 *bufptr++ = v < 0 ? '0' : val[v];
404                 if (comma && (thousands = (thousands + 1) % 3) == 0 &&
405                         v > 0 && thsep != '\0')
406                     *bufptr++ = thsep;
407                 v--;
408             }
409         } else if (f >= 0) {
410             *bufptr++ = fmt[f];
411         }
412         if (v >= 0 && f == digit) {
413             continue;
414         }
415         f--;
416     }
417 
418     if (negative && digit >= 0)
419         *bufptr++ = '-';
420     *bufptr = EOS;
421     reverse(buf);
422 
423     return (buf);
424 }
425 
426 /**
427  * \brief TODO Document fmt_frac()
428  *
429  * \param[in] val fractional part of the value to be formatted
430  * \param[in] fmt fractional portion of format
431  * \param[in] precision, for interpreting the "&"
432  *
433  * \return none
434  */
435 
fmt_frac(char * val,char * fmt,int lprecision)436 static char * fmt_frac(char *val, char *fmt, int lprecision) {
437 
438     static char buf[MAXBUF];
439     register char * bufptr = buf;
440     register char * fmtptr = fmt, *valptr = val;
441 
442     *bufptr++ = dpoint;
443     while (*fmtptr != EOS) {
444         if (*fmtptr == '&') {
445             int i;
446             for (i = 0; i < lprecision; i++)
447                 *bufptr++ = (*valptr != EOS) ? *valptr++ : '0';
448         } else if (*fmtptr == '\\')
449             *bufptr++ = *++fmtptr;
450         else if (*fmtptr == '#' || *fmtptr == '0') {
451             if (*valptr != EOS || *fmtptr == '0')
452                 *bufptr++ = (*valptr != EOS) ? *valptr++ : '0';
453         } else
454             *bufptr++ = *fmtptr;
455         fmtptr++;
456     }
457     *bufptr = EOS;
458 
459     if (buf[1] < '0' || buf[1] > '9')
460         return (buf + 1);
461     else
462         return (buf);
463 }
464 
465 /**
466  * \brief TODO Document fmt_exp
467  *
468  * \param[in] val value of the exponent
469  * \param[in] fmt exponent part of the format
470  *
471  * \return none
472  */
473 
fmt_exp(int val,char * fmt)474 static char * fmt_exp(int val, char *fmt) {
475     static char buf[MAXBUF];
476     register char *bufptr = buf;
477     char valbuf[64];
478     int negative = false;
479 
480     *bufptr++ = *fmt++;
481     if (*fmt == '+')
482         *bufptr++ = (val < 0) ? '-' : '+';
483     else if (val < 0)
484         *bufptr++ = '-';
485     fmt++;
486     *bufptr = EOS;
487 
488     if (val < 0) {
489         val = -val;
490         negative = false;
491     }
492     (void) sprintf(valbuf, "%d", val);
493 
494     (void) strcat(buf, fmt_int(valbuf, fmt, false, negative));
495     return (buf);
496 }
497 
498 /**
499  * \brief TODO Document reverse()
500  *
501  * \param[in] buf
502  *
503  * \return none
504  */
505 
reverse(register char * buf)506 static void reverse(register char *buf) {
507     register char *cp = buf + strlen(buf) - 1;
508     register char tmp;
509 
510     while (buf < cp) {
511         tmp = *cp;
512         *cp-- = *buf;
513         *buf++ = tmp;
514     }
515 }
516 
517 /*
518  * Tom Anderson    <toma@hpsad.hp.com>
519  * 10/14/90
520  *
521  * This routine takes a value and formats it using fixed, scientific,
522  * or engineering notation.  The format command 'f' determines which
523  * format is used.  The formats are:         example
524  *    0:   Fixed point (default)             0.00010
525  *    1:   Scientific                        1.00E-04
526  *    2:   Engineering                       100.00e-06
527  *
528  * The format command 'f' now uses three values.  The first two are the
529  * width and precision, and the last one is the format value 0, 1, or 2 as
530  * described above.  The format value is passed in the variable fmt.
531  *
532  * This formatted value is written into the passed buffer.  if the
533  * resulting string is too long to fit into the passed buffer, the
534  * function returns false.  Otherwise the function returns true.
535  *
536  * When a number is formatted as engineering and is outside of the range,
537  * the format reverts to scientific.
538  *
539  * To preserve compatability with old spreadsheet files, the third value
540  * may be missing, and the default will be fixed point (format 0).
541  *
542  * When an old style sheet is saved, the third value will be stored.
543  */
544 
545 /* defined in sc.h */
546 #ifndef REFMTFIX
547 #define REFMTFIX    0
548 #define REFMTFLT    1
549 #define REFMTENG    2
550 #define REFMTDATE   3
551 #define REFMTLDATE  4
552 #endif
553 
554 /**
555  * \brief TODO Document engformat()
556  *
557  * \param[in] fmt
558  * \param[in] width
559  * \param[in] lprecision
560  * \param[in] val
561  * \param[in] buf
562  * \param[in] buflen
563  *
564  * \return none
565  */
566 
engformat(int fmt,int width,int lprecision,double val,char * buf,int buflen)567 int engformat(int fmt, int width, int lprecision, double val, char *buf, int buflen) {
568 
569     static char * engmult[] = {
570     "-18", "-15", "-12", "-09", "-06", "-03",
571     "+00",
572     "+03", "+06", "+09", "+12", "+15", "+18"
573     };
574     int engind = 0;
575     double engmant, pow(), engabs, engexp;
576 
577     if (buflen < width) return (false);
578     if (fmt >= 0 && fmt < COLFORMATS && colformat[fmt])
579         return (format(colformat[fmt], lprecision, val, buf, buflen));
580     if (fmt == REFMTFIX)
581         (void) sprintf(buf,"%*.*f", width, lprecision, val);
582     if (fmt == REFMTFLT)
583         (void) sprintf(buf,"%*.*e", width, lprecision, val);
584     if (fmt == REFMTENG) {
585         if (val == 0e0) {    /* Hack to get zeroes to line up in engr fmt */
586             (void) sprintf((buf-1),"%*.*f ", width, lprecision, val);
587         } else {
588             engabs = (val);
589             if ( engabs <  0e0)       engabs = -engabs;
590             if ((engabs >= 1e-18) && (engabs <  1e-15)) engind=0;
591             if ((engabs >= 1e-15) && (engabs <  1e-12)) engind=1;
592             if ((engabs >= 1e-12) && (engabs <  1e-9 )) engind=2;
593             if ((engabs >= 1e-9)  && (engabs <  1e-6 )) engind=3;
594             if ((engabs >= 1e-6)  && (engabs <  1e-3 )) engind=4;
595             if ((engabs >= 1e-3)  && (engabs <  1    )) engind=5;
596             if ((engabs >= 1)     && (engabs <  1e3  )) engind=6;
597             if ((engabs >= 1e3)   && (engabs <  1e6  )) engind=7;
598             if ((engabs >= 1e6)   && (engabs <  1e9  )) engind=8;
599             if ((engabs >= 1e9)   && (engabs <  1e12 )) engind=9;
600             if ((engabs >= 1e12)  && (engabs <  1e15 )) engind=10;
601             if ((engabs >= 1e15)  && (engabs <  1e18 )) engind=11;
602             if ((engabs >= 1e18)  && (engabs <  1e21 )) engind=12;
603             if ((engabs < 1e-18)  || (engabs >= 1e21 )) {
604                 /* Revert to floating point */
605                 (void) sprintf(buf,"%*.*e", width, lprecision, val);
606             } else {
607                 engexp = (double) (engind-6)*3;
608                 engmant = val/pow(10.0e0,engexp);
609                 (void) sprintf(buf,"%*.*fe%s", width-4, lprecision, engmant, engmult[engind]);
610             }
611         }
612     }
613     if (fmt == REFMTDATE) {
614         int i;
615         time_t secs;
616 
617         if (buflen < 9) {
618             for (i = 0; i < width; i++) buf[i] = '*';
619             buf[i] = '\0';
620         } else {
621             secs = (time_t)val;
622             strftime(buf,buflen,"%e %b %y",localtime(&secs));
623             for (i = 9; i < width; i++) buf[i] = ' ';
624             buf[i] = '\0';
625         }
626     }
627     if (fmt == REFMTLDATE) {
628         int i;
629         time_t secs;
630 
631         if (buflen < 11) {
632             for (i = 0; i < width; i++) buf[i] = '*';
633             buf[i] = '\0';
634         } else {
635             secs = (time_t)val;
636             strftime(buf,buflen,"%e %b %Y",localtime(&secs));
637             for (i = 11; i < width; i++) buf[i] = ' ';
638             buf[i] = '\0';
639         }
640     }
641     return (true);
642 }
643