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 file.c
40  * \author Andrés Martinelli <andmarti@gmail.com>
41  * \date 2017-07-18
42  * \brief TODO Write a brief file description.
43  */
44 
45 #include <pwd.h>
46 #include <sys/stat.h>
47 #include <time.h>
48 #include <utime.h>
49 #include <fcntl.h>
50 #include <signal.h>
51 #include <errno.h>
52 #include <stdlib.h>
53 #include <unistd.h>
54 #include <wchar.h>
55 #include <sys/wait.h>
56 
57 #ifndef NO_WORDEXP
58 #include <wordexp.h>
59 #endif
60 
61 #include "conf.h"
62 #include "maps.h"
63 #include "yank.h"
64 #include "cmds.h"
65 #include "file.h"
66 #include "marks.h"
67 #include "lex.h"
68 #include "format.h"
69 #include "interp.h"
70 #include "utils/string.h"
71 #include "utils/dictionary.h"
72 #include "cmds_edit.h"
73 #include "xmalloc.h"
74 #include "y.tab.h"
75 #include "xlsx.h"
76 #include "ods.h"
77 #include "xls.h"
78 #include "tui.h"
79 
80 extern struct ent * freeents;
81 extern int yyparse(void);
82 
83 #ifdef HAVE_PTHREAD
84 #include <pthread.h>
85 extern pthread_t fthread;
86 extern int pthread_exists;
87 #endif
88 
89 /**
90  * \brief Erase the database (tbl, etc.)
91  *
92  * \return none
93  */
erasedb()94 void erasedb() {
95     int  r, c;
96 
97     for (c = 0; c <= maxcol; c++) {
98         fwidth[c] = DEFWIDTH;
99         precision[c] = DEFPREC;
100         realfmt[c] = DEFREFMT;
101     }
102 
103     for (r = 0; r <= maxrow; r++) {
104         row_format[r] = 1;
105         register struct ent ** pp = ATBL(tbl, r, 0);
106         for (c = 0; c++ <= maxcol; pp++)
107             if (*pp != NULL) {
108                 //(*pp)->next = freeents;    /* save [struct ent] for reuse */
109                 //freeents = *pp;
110 
111                 clearent(*pp);
112             }
113     }
114 
115     for (c = 0; c < COLFORMATS; c++) {
116         if (colformat[c] != NULL)
117             scxfree(colformat[c]);
118         colformat[c] = NULL;
119     }
120 
121     maxrow = 0;
122     maxcol = 0;
123 
124     clean_range();
125 
126     calc_order = BYROWS;
127     prescale = 1.0;
128     tbl_style = 0;
129     optimize = 0;
130     currow = curcol = 0;
131 
132     *curfile = '\0';
133 }
134 
135 /**
136  * \brief load rc config file
137  *
138  * \return none
139  */
loadrc(void)140 void loadrc(void) {
141     char rcpath[PATHLEN];
142     char * home;
143 
144     if ((home = getenv("XDG_CONFIG_HOME"))) {
145         char config_dir[PATHLEN-(sizeof CONFIG_FILE)];
146         snprintf(config_dir, PATHLEN-(sizeof CONFIG_FILE), "%s/sc-im", home);
147         mkdir(config_dir,0777);
148         snprintf(rcpath, PATHLEN, "%s/%s", config_dir, CONFIG_FILE);
149         (void) readfile(rcpath, 0);
150     }
151     /* Default to compile time if XDG_CONFIG_HOME not found */
152     else if ((home = getenv("HOME"))) {
153         char config_dir[PATHLEN];
154         sprintf(config_dir, "%s/%s", home,CONFIG_DIR);
155         mkdir(config_dir,0777);
156         snprintf(rcpath, PATHLEN, "%s/%s/%s", home,CONFIG_DIR,CONFIG_FILE);
157         (void) readfile(rcpath, 0);
158     }
159     *curfile = '\0';
160 }
161 
162 /**
163  * \brief Check if a file exists
164  *
165  * \details Check if a file exists. Returns 1 if so. Returns 0 otherwise.
166  *
167  * \param[in] fname file name
168  *
169  * \return 1 if file exises; 0 otherwise
170  */
171 
file_exists(const char * fname)172 int file_exists(const char * fname) {
173     FILE * file;
174     if ((file = fopen(fname, "r"))) {
175         fclose(file);
176         return 1;
177     }
178     return 0;
179 }
180 
181 /**
182  * \brief Check if file has been modified since last save.
183  *
184  * \details This function checks if a file suffered mods since it was open.
185  *
186  * \return 0 if not modified; 1 if modified
187  */
modcheck()188 int modcheck() {
189     if (modflg && ! get_conf_int("nocurses")) {
190         sc_error("File not saved since last change. Add '!' to force");
191         return(1);
192     }
193     return 0;
194 }
195 
196 /**
197  * \brief Return the proper delimiter for delimiter separated files.
198  *
199  * \details This function checks the type of a file as well as txtdelim conf value
200  *
201  * \return one of , ; \t |
202  */
get_delim(char * type)203 char get_delim(char *type) {
204     char delim = ',';
205     if (!strcasecmp(type, "tsv") || !strcasecmp(type, "tab"))
206         delim = '\t';
207 
208     if (get_conf_value("txtdelim") != NULL) {
209         if (!strcasecmp(get_conf_value("txtdelim"), "\\t")) {
210             delim = '\t';
211         } else if (!strcasecmp(get_conf_value("txtdelim"), ",")) {
212             delim = ',';
213         } else if (!strcasecmp(get_conf_value("txtdelim"), ";")) {
214             delim = ';';
215         } else if (!strcasecmp(get_conf_value("txtdelim"), "|")) {
216             delim = '|';
217         }
218     }
219     return delim;
220 }
221 
222 /**
223  * \brief TODO Handle the save file process
224  *
225  * This funciton handles the save file process in SC-IM format..
226  *
227  * \return 0 on OK; -1 on error
228  */
savefile()229 int savefile() {
230     int force_rewrite = 0;
231     char name[BUFFERSIZE];
232 #ifndef NO_WORDEXP
233     size_t len;
234     wordexp_t p;
235 #endif
236 
237     if (! curfile[0] && wcslen(inputline) < 3) { // casos ":w" ":w!" ":x" ":x!"
238         sc_error("There is no filename");
239         return -1;
240     }
241 
242     if (inputline[1] == L'!') force_rewrite = 1;
243 
244     wcstombs(name, inputline, BUFFERSIZE);
245     del_range_chars(name, 0, 1 + force_rewrite);
246 
247 #ifndef NO_WORDEXP
248     wordexp(name, &p, 0);
249     if (p.we_wordc < 1) {
250         sc_error("Failed expanding filepath");
251         return -1;
252     }
253     if ((len = strlen(p.we_wordv[0])) >= sizeof(name)) {
254         sc_error("File path too long");
255         wordfree(&p);
256         return -1;
257     }
258     memcpy(name, p.we_wordv[0], len+1);
259     wordfree(&p);
260 #endif
261 
262     if (! force_rewrite && file_exists(name)) {
263         sc_error("File already exists. Use \"!\" to force rewrite.");
264         return -1;
265     }
266 
267 #ifdef AUTOBACKUP
268     // check if backup of curfile exists.
269     // if it exists, remove it.
270     if (strlen(curfile) && backup_exists(curfile)) remove_backup(curfile);
271 
272     // check if backup of newfilename exists.
273     // if it exists and '!' is set, remove it.
274     // if it exists and no '!' is set, return.
275     if (!strlen(curfile) && backup_exists(name)) {
276         if (!force_rewrite) {
277             sc_error("Backup file of %s exists. Use \"!\" to force the write process.", name);
278             return -1;
279         } else remove_backup(name);
280     }
281 #endif
282 
283     // copy newfilename to curfile
284     if (wcslen(inputline) > 2) {
285         strcpy(curfile, name);
286     }
287 
288     // add sc extension if not present
289     if (wcslen(inputline) > 2 && str_in_str(curfile, ".") == -1) {
290         sprintf(curfile + strlen(curfile), ".sc");
291 
292     // treat csv
293     } else if (strlen(curfile) > 4 && (! strcasecmp( & curfile[strlen(curfile)-4], ".csv"))) {
294         export_delim(curfile, get_delim("csv"), 0, 0, maxrow, maxcol, 1);
295         modflg = 0;
296         return 0;
297 
298     // treat tab
299     } else if (strlen(curfile) > 4 && (! strcasecmp( & curfile[strlen(curfile)-4], ".tsv") ||
300         ! strcasecmp( & curfile[strlen(curfile)-4], ".tab"))){
301         export_delim(curfile, '\t', 0, 0, maxrow, maxcol, 1);
302         modflg = 0;
303         return 0;
304 
305     // treat markdown format
306     } else if (strlen(curfile) > 3 && ( ! strcasecmp( & curfile[strlen(curfile)-3], ".md") ||
307           ! strcasecmp( & curfile[strlen(curfile)-4], ".mkd"))){
308       export_markdown(curfile, 0, 0, maxrow, maxcol);
309       modflg = 0;
310       return 0;
311 
312     // treat xlsx format
313     } else if (strlen(curfile) > 5 && ( ! strcasecmp( & curfile[strlen(curfile)-5], ".xlsx") ||
314             ! strcasecmp( & curfile[strlen(curfile)-5], ".xlsx"))){
315 #ifndef XLSX_EXPORT
316         sc_error("XLSX export support not compiled in. Please save file in other extension.");
317         return -1;
318 #else
319         if (export_xlsx(curfile, 0, 0, maxrow, maxcol) == 0) {
320             sc_info("File \"%s\" written", curfile);
321             modflg = 0;
322         } else
323             sc_error("File could not be saved");
324         return 0;
325 #endif
326     }
327 
328     // save in sc format
329     if (writefile(curfile, 0, 0, maxrow, maxcol, 1) < 0) {
330         sc_error("File could not be saved");
331         return -1;
332     }
333     modflg = 0;
334     return 0;
335 }
336 
337 /**
338  * \brief Write a file
339  *
340  * \details Write a file. Receives parameter range and file name.
341  *
342  * \param[in] fname file name
343  * \param[in] r0
344  * \param[in] c0
345  * \param[in] rn
346  * \param[in] cn
347  * \param[in] verbose
348  *
349  * \return 0 on success; -1 on error
350  */
writefile(char * fname,int r0,int c0,int rn,int cn,int verbose)351 int writefile(char * fname, int r0, int c0, int rn, int cn, int verbose) {
352     register FILE *f;
353     char save[PATHLEN];
354     char tfname[PATHLEN];
355     int pid;
356 
357     (void) strcpy(tfname, fname);
358 
359     (void) strcpy(save, tfname);
360 
361     if ((f = openfile(tfname, &pid, NULL)) == NULL) {
362         if (verbose) sc_error("Can't create file \"%s\"", save);
363         return -1;
364     }
365 
366     if (verbose) sc_info("Writing file \"%s\"...", save);
367     write_fd(f, r0, c0, rn, cn);
368 
369     closefile(f, pid, 0);
370 
371     if (! pid) {
372         (void) strcpy(curfile, save);
373         modflg = 0;
374         if (verbose) sc_info("File \"%s\" written", curfile);
375     }
376 
377     return 0;
378 }
379 
380 /**
381  * \brief TODO Document write_fd
382  *
383  * \param[in] f file pointer
384  * \param[in] r0
385  * \param[in] c0
386  * \param[in] rn
387  * \param[in] cn
388  *
389  * \return none
390  */
write_fd(register FILE * f,int r0,int c0,int rn,int cn)391 void write_fd(register FILE *f, int r0, int c0, int rn, int cn) {
392     register struct ent **pp;
393     int r, c;
394 
395     (void) fprintf(f, "# This data file was generated by the Spreadsheet Calculator Improvised (SC-IM)\n");
396     (void) fprintf(f, "# You almost certainly shouldn't edit it.\n\n");
397     print_options(f);
398     for (c = 0; c < COLFORMATS; c++)
399         if (colformat[c])
400             (void) fprintf (f, "format %d = \"%s\"\n", c, colformat[c]);
401 
402     for (c = c0; c <= cn; c++)
403         if (fwidth[c] != DEFWIDTH || precision[c] != DEFPREC || realfmt[c] != DEFREFMT)
404             (void) fprintf (f, "format %s %d %d %d\n", coltoa(c), fwidth[c], precision[c], realfmt[c]);
405 
406     for (r = r0; r <= rn; r++)
407         if (row_format[r] != 1)
408             (void) fprintf (f, "format %d %d\n", r, row_format[r]);
409 
410     // new implementation of hidecol. group by ranges
411     for (c = c0; c <= cn; c++) {
412         int c_aux = c;
413         if ( col_hidden[c] && c <= maxcol && ( c == 0 || !col_hidden[c-1] )) {
414             while (c_aux <= maxcol && col_hidden[c_aux])
415                 c_aux++;
416             fprintf(f, "hidecol %s", coltoa(c));
417             if (c_aux-1 != c) {
418                 fprintf(f, ":%s\n", coltoa(c_aux-1));
419                 c = c_aux-1;
420             } else
421                 fprintf(f, "\n");
422         }
423     }
424 
425     // new implementation of hiderow. group by ranges
426     for (r = r0; r <= rn; r++) {
427         int r_aux = r;
428         if ( row_hidden[r] && r <= maxrow && ( r == 0 || !row_hidden[r-1] )) {
429             while (r_aux <= maxrow && row_hidden[r_aux])
430                 r_aux++;
431             fprintf(f, "hiderow %d", r);
432             if (r_aux-1 != r) {
433                 fprintf(f, ":%d\n", r_aux-1);
434                 r = r_aux-1;
435             } else
436                 fprintf(f, "\n");
437         }
438     }
439 
440     // frozen cols. group by ranges
441     for (c = c0; c <= cn; c++) {
442         int c_aux = c;
443         if (col_frozen[c] && c <= maxcol && (c == 0 || ! col_frozen[c-1])) {
444             while (c_aux <= maxcol && col_frozen[c_aux]) c_aux++;
445             fprintf(f, "freeze %s", coltoa(c));
446             if (c_aux-1 != c) {
447                 fprintf(f, ":%s\n", coltoa(c_aux-1));
448                 c = c_aux-1;
449             } else
450                 fprintf(f, "\n");
451         }
452     }
453 
454     // frozen rows. group by ranges
455     for (r = r0; r <= rn; r++) {
456         int r_aux = r;
457         if (row_frozen[r] && r <= maxrow && (r == 0 || ! row_frozen[r-1])) {
458             while (r_aux <= maxrow && row_frozen[r_aux]) r_aux++;
459             fprintf(f, "freeze %d", r);
460             if (r_aux-1 != r) {
461                 fprintf(f, ":%d\n", r_aux-1);
462                 r = r_aux-1;
463             } else
464                 fprintf(f, "\n");
465         }
466     }
467 
468     write_marks(f);
469     write_franges(f);
470 
471     write_cells(f, r0, c0, rn, cn, r0, c0);
472 
473     struct custom_color * cc;
474     for (r = r0; r <= rn; r++) {
475         pp = ATBL(tbl, r, c0);
476         for (c = c0; c <= cn; c++, pp++)
477             if (*pp) {
478                 // Write ucolors
479                 if ((*pp)->ucolor != NULL) {
480                     char strcolorbuf[BUFFERSIZE];
481                     char * strcolor = strcolorbuf;
482                     strcolor[0] = 0;
483                     strcolor[1] = 0;
484 
485                     // decompile int value of color to its string description
486                     if ((*pp)->ucolor->fg != NONE_COLOR) {
487                         if ((*pp)->ucolor->fg <= 8) {
488                             linelim=0;
489                             struct enode * e = new((*pp)->ucolor->fg, (struct enode *)0, (struct enode *)0);
490                             decompile(e, 0);
491                             uppercase(line);
492                             del_char(line, 0);
493                             sprintf(strcolor, " fg=%.*s", BUFFERSIZE-5, &line[0]);
494                             free(e);
495                         } else if ((cc = get_custom_color_by_number((*pp)->ucolor->fg - 7)) != NULL) {
496                             sprintf(strcolor, " fg=%.*s", BUFFERSIZE, cc->name);
497                         }
498                     }
499 
500                     if ((*pp)->ucolor->bg != NONE_COLOR) {
501                         if ((*pp)->ucolor->bg <= WHITE) {
502                             linelim=0;
503                             struct enode * e = new((*pp)->ucolor->bg, (struct enode *)0, (struct enode *)0);
504                             decompile(e, 0);
505                             uppercase(line);
506                             del_char(line, 0);
507                             sprintf(strcolor + strlen(strcolor), " bg=%s", &line[0]);
508                             free(e);
509                         } else if ((cc = get_custom_color_by_number((*pp)->ucolor->bg - 7)) != NULL) {
510                             sprintf(strcolor + strlen(strcolor), " bg=%.*s", BUFFERSIZE, cc->name);
511                         }
512                     }
513 
514                     if ((*pp)->ucolor->bold)      sprintf(strcolor + strlen(strcolor), " bold=1");
515                     if ((*pp)->ucolor->italic)    sprintf(strcolor + strlen(strcolor), " italic=1");
516                     if ((*pp)->ucolor->dim)       sprintf(strcolor + strlen(strcolor), " dim=1");
517                     if ((*pp)->ucolor->reverse)   sprintf(strcolor + strlen(strcolor), " reverse=1");
518                     if ((*pp)->ucolor->standout)  sprintf(strcolor + strlen(strcolor), " standout=1");
519                     if ((*pp)->ucolor->underline) sprintf(strcolor + strlen(strcolor), " underline=1");
520                     if ((*pp)->ucolor->blink)     sprintf(strcolor + strlen(strcolor), " blink=1");
521 
522                     // Remove the leading space
523                     strcolor++;
524 
525                     // previous implementation
526                     //(void) fprintf(f, "cellcolor %s%d \"%s\"\n", coltoa((*pp)->col), (*pp)->row, strcolor);
527 
528                     // new implementation
529                     // by row, store cellcolors grouped by ranges
530                     int c_aux = c;
531                     struct ucolor * u = (*pp)->ucolor;
532                     struct ucolor * a = NULL;
533                     if ( c > 0 && *ATBL(tbl, r, c-1) != NULL)
534                          a = (*ATBL(tbl, r, c-1))->ucolor;
535 
536                     if ( *strcolor != '\0' && (u != NULL) && (c <= maxcol) && ( c == 0 || ( a == NULL ) || ( a != NULL && ! same_ucolor( a, u ) ))) {
537                         while (c_aux <= maxcol && *ATBL(tbl, r, c_aux) != NULL && same_ucolor( (*ATBL(tbl, r, c_aux))->ucolor, (*pp)->ucolor ))
538                             c_aux++;
539                         fprintf(f, "cellcolor %s%d", coltoa((*pp)->col), (*pp)->row);
540                         if (c_aux-1 != (*pp)->col)
541                             fprintf(f, ":%s%d \"%s\"\n", coltoa(c_aux-1), (*pp)->row, strcolor);
542                         else
543                             fprintf(f, " \"%s\"\n", strcolor);
544                     }
545 
546                 }
547 
548 
549                 /* if ((*pp)->nrow >= 0) {
550                     (void) fprintf(f, "addnote %s ", v_name((*pp)->row, (*pp)->col));
551                     (void) fprintf(f, "%s\n", r_name((*pp)->nrow, (*pp)->ncol, (*pp)->nlastrow, (*pp)->nlastcol));
552                 } */
553 
554                 // padding
555                 // previous implementation
556                 //if ((*pp)->pad)
557                 //    (void) fprintf(f, "pad %d %s%d\n", (*pp)->pad, coltoa((*pp)->col), (*pp)->row);
558                 // new implementation
559                 int r_aux = r;
560                 if ( (*pp)->pad  && r <= maxrow && ( r == 0 || (*ATBL(tbl, r-1, c) == NULL) ||
561                     (*ATBL(tbl, r-1, c) != NULL && ((*ATBL(tbl, r-1, c))->pad != (*pp)->pad)) )) {
562                     while (r_aux <= maxrow && *ATBL(tbl, r_aux, c) != NULL && (*pp)->pad == (*ATBL(tbl, r_aux, c))->pad )
563                         r_aux++;
564                     fprintf(f, "pad %d %s%d", (*pp)->pad, coltoa((*pp)->col), (*pp)->row);
565                     if (r_aux-1 != (*pp)->row)
566                         fprintf(f, ":%s%d\n", coltoa((*pp)->col), r_aux-1);
567                     else
568                         fprintf(f, "\n");
569                 }
570             }
571     }
572 
573     // write locked cells
574     // lock should be stored after any other command
575     for (r = r0; r <= rn; r++) {
576         pp = ATBL(tbl, r, c0);
577         for (c = c0; c <= cn; c++, pp++)
578             if (*pp) {
579                 // previous implementation
580                 //if ((*pp)->flags & is_locked)
581                 //    (void) fprintf(f, "lock %s%d\n", coltoa((*pp)->col), (*pp)->row);
582                 // new implementation
583                 int c_aux = c;
584                 if ( (*pp)->flags & is_locked && c <= maxcol && ( c == 0 || ( *ATBL(tbl, r, c-1) != NULL && ! ((*ATBL(tbl, r, c-1))->flags & is_locked) ) )) {
585                     while (c_aux <= maxcol && *ATBL(tbl, r, c_aux) != NULL && (*ATBL(tbl, r, c_aux))->flags & is_locked )
586                         c_aux++;
587                     fprintf(f, "lock %s%d", coltoa((*pp)->col), (*pp)->row);
588                     if (c_aux-1 != (*pp)->col)
589                         fprintf(f, ":%s%d\n", coltoa(c_aux-1), (*pp)->row);
590                     else
591                         fprintf(f, "\n");
592                 }
593             }
594     }
595 
596     /*
597      * Don't try to combine these into a single fprintf().  v_name() has
598      * a single buffer that is overwritten on each call, so the first part
599      * needs to be written to the file before making the second call.
600      */
601     fprintf(f, "goto %s", v_name(currow, curcol));
602     //fprintf(f, " %s\n", v_name(strow, stcol));
603     fprintf(f, "\n");
604 }
605 
606 /**
607  * \brief TODO Document write_franges()
608  *
609  * \param[in] f file pointer
610  *
611  * \return none
612  */
write_franges(register FILE * f)613 void write_franges(register FILE *f) {
614     if (! freeze_ranges) return;
615     if (freeze_ranges->type == 'a') {
616         fprintf(f, "freeze %s%d", coltoa(freeze_ranges->tl->col), freeze_ranges->tl->row);
617         fprintf(f, ":%s%d\n", coltoa(freeze_ranges->br->col), freeze_ranges->br->row);
618     } else if (freeze_ranges->type == 'c' && freeze_ranges->tl->col == freeze_ranges->br->col) {
619         fprintf(f, "freeze %s\n", coltoa(freeze_ranges->tl->col));
620     } else if (freeze_ranges->type == 'c') {
621         fprintf(f, "freeze %s:", coltoa(freeze_ranges->tl->col));
622         fprintf(f, "%s\n", coltoa(freeze_ranges->br->col));
623     } else if (freeze_ranges->type == 'r' && freeze_ranges->tl->row == freeze_ranges->br->row) {
624         fprintf(f, "freeze %d\n", freeze_ranges->tl->row);
625     } else if (freeze_ranges->type == 'r') {
626         fprintf(f, "freeze %d:%d\n", freeze_ranges->tl->row, freeze_ranges->br->row);
627     }
628 }
629 
630 /**
631  * \brief TODO Document write_marks()
632  *
633  * \param[in] f file pointer
634  *
635  * \return none
636  */
write_marks(register FILE * f)637 void write_marks(register FILE *f) {
638     int i;
639     struct mark * m;
640 
641     for ( i='a'; i<='z'; i++ ) {
642         m = get_mark((char) i);
643 
644         // m->rng should never be NULL if both m->col and m->row are -1 !!
645         if ( m->row == -1 && m->col == -1) { // && m->rng != NULL ) {
646             fprintf(f, "mark %c %s%d ", i, coltoa(m->rng->tlcol), m->rng->tlrow);
647             fprintf(f, "%s%d\n", coltoa(m->rng->brcol), m->rng->brrow);
648         } else if ( m->row != 0 && m->row != 0) { // && m->rng == NULL) {
649             fprintf(f, "mark %c %s%d\n", i, coltoa(m->col), m->row);
650         }
651     }
652 
653     return;
654 }
655 
656 /**
657  * \brief TODO Document write_cells()
658  *
659  * \param[in] f file pointer
660  * \param[in] r0
661  * \param[in] c0
662  * \param[in] rn
663  * \param[in] cn
664  * \param[in] dr
665  * \param[in[ dc
666  *
667  * \return none
668  */
write_cells(register FILE * f,int r0,int c0,int rn,int cn,int dr,int dc)669 void write_cells(register FILE *f, int r0, int c0, int rn, int cn, int dr, int dc) {
670     register struct ent **pp;
671     int r, c;
672     //int r, c, mf;
673     char *dpointptr;
674 
675     //mf = modflg;
676     if (dr != r0 || dc != c0) {
677         //yank_area(r0, c0, rn, cn);
678         rn += dr - r0;
679         cn += dc - c0;
680         //rs = currow;
681         //cs = curcol;
682         currow = dr;
683         curcol = dc;
684     }
685     //if (Vopt) valueize_area(dr, dc, rn, cn);
686     for (r = dr; r <= rn; r++) {
687         pp = ATBL(tbl, r, dc);
688         for (c = dc; c <= cn; c++, pp++)
689             if (*pp) {
690                 if ((*pp)->label || (*pp)->flags & is_strexpr) {
691                     edits(r, c, 1);
692                     (void) fprintf(f, "%s\n", line);
693                 }
694                 if ((*pp)->flags & is_valid) {
695                 //if ((*pp)->flags & is_valid || (*pp)->expr) { // for #541
696                     editv(r, c);
697                     dpointptr = strchr(line, dpoint);
698                     if (dpointptr != NULL)
699                         *dpointptr = '.';
700                     (void) fprintf(f, "%s\n", line);
701                 }
702                 if ((*pp)->format) {
703                     editfmt(r, c);
704                     (void) fprintf(f, "%s\n",line);
705                 }
706             }
707     }
708     //modflg = mf;
709 }
710 
711 /**
712  * \brief Try to open a spreadsheet file.
713  *
714  * \param[in] fname file name
715  * \param[in] eraseflg
716  *
717  * \return SC_READFILE_SUCCESS if we loaded the file, SC_READFILE_ERROR if we failed,
718  * SC_READFILE_DOESNTEXIST if the file doesn't exist.
719  */
readfile(char * fname,int eraseflg)720 sc_readfile_result readfile(char * fname, int eraseflg) {
721     if (!strlen(fname)) return 0;
722     loading = 1;
723 
724 #ifdef AUTOBACKUP
725     // Check if curfile is set and backup exists..
726     if (str_in_str(fname, CONFIG_FILE) == -1 && strlen(curfile) &&
727     backup_exists(curfile) && strcmp(fname, curfile)) {
728         if (modflg) {
729             // TODO - force load with '!' ??
730             sc_error("There are changes unsaved. Cannot load file: %s", fname);
731             loading = 0;
732             return SC_READFILE_ERROR;
733         }
734         remove_backup(curfile);
735     }
736     // Check if fname is set and backup exists..
737     if (backup_exists(fname)) {
738         wchar_t msg[BUFFERSIZE];
739         swprintf(msg, BUFFERSIZE,
740         // TODO - Open backup readonly ??
741         L"Backup of %s file exists. Do you want to (E)dit the file and remove the backup, (R)ecover the backup or (Q)uit: ", fname);
742         wchar_t t = ui_query_opt(msg, L"qerQER");
743         switch (t) {
744             case L'q':
745             case L'Q':
746                 loading = 0;
747                 extern int shall_quit;
748                 shall_quit = 1;
749                 return SC_READFILE_ERROR;
750                 break;
751             case L'e':
752             case L'E':
753                 remove_backup(fname);
754                 break;
755             case L'r':
756             case L'R':
757                 ;
758                 int len = strlen(fname);
759                 if (!len) return 0;
760                 char * pstr = strrchr(fname, '/');
761                 int pos = pstr == NULL ? -1 : pstr - fname;
762                 char bkpname[len+6];
763                 strcpy(bkpname, fname);
764                 add_char(bkpname, '.', pos+1);
765                 sprintf(bkpname + strlen(bkpname), ".bak");
766                 remove(fname);
767                 rename(bkpname, fname);
768                 break;
769         }
770     }
771 #endif
772 
773     // Check if file is a correct format
774     int len = strlen(fname);
775     if (! strcmp( & fname[len-3], ".sc") ||
776         (len >= strlen(CONFIG_FILE) && ! strcasecmp( & fname[len-strlen(CONFIG_FILE)], CONFIG_FILE))) {
777         // pass
778 
779     // If file is an xlsx file, we import it
780     } else if (len > 5 && ! strcasecmp( & fname[len-5], ".xlsx")){
781         #ifndef XLSX
782         sc_error("XLSX import support not compiled in");
783         #else
784         open_xlsx(fname, "UTF-8");
785         strcpy(curfile, fname);
786         modflg = 0;
787         #endif
788         loading = 0;
789         return SC_READFILE_SUCCESS;
790 
791     // If file is an ODS file, we import it
792     } else if (len > 4 && ! strcasecmp( & fname[len-4], ".ods")){
793         #ifndef ODS
794         sc_error("ODS import support not compiled in");
795         #else
796         open_ods(fname, "UTF-8");
797         strcpy(curfile, fname);
798         modflg = 0;
799         #endif
800         loading = 0;
801         return SC_READFILE_SUCCESS;
802 
803     // If file is an xls file, we import it
804     } else if (len > 4 && ! strcasecmp( & fname[len-4], ".xls")){
805         #ifndef XLS
806         sc_error("XLS import support not compiled in");
807         #else
808         open_xls(fname, "UTF-8");
809         modflg = 0;
810         strcpy(curfile, fname);
811         #endif
812         loading = 0;
813         return SC_READFILE_SUCCESS;
814 
815     // If file is an delimited text file, we import it
816     } else if (len > 4 && ( ! strcasecmp( & fname[len-4], ".csv") ||
817         ! strcasecmp( & fname[len-4], ".tsv") || ! strcasecmp( & fname[len-4], ".tab") ||
818         ! strcasecmp( & fname[len-4], ".txt") )){
819 
820         import_csv(fname, get_delim(&fname[len-3])); // csv tsv tab txt delim import
821         strcpy(curfile, fname);
822         modflg = 0;
823         loading = 0;
824         return SC_READFILE_SUCCESS;
825 
826     // If file is a markdown text file, we try to import it
827     } else if (len > 3 && ( ! strcasecmp( & fname[len-3], ".md") ||
828           ! strcasecmp( & fname[len-4], ".mkd"))){
829 
830       import_markdown(fname);
831       strcpy(curfile, fname);
832       modflg = 0;
833       loading = 0;
834       return SC_READFILE_SUCCESS;
835 
836     } else {
837         sc_info("\"%s\" is not a SC-IM compatible file", fname);
838         loading = 0;
839         return SC_READFILE_ERROR;
840     }
841 
842     // We open an 'sc' format file
843     // open fname for reading
844     register FILE * f;
845     char save[PATHLEN];
846     if (*fname == '\0') fname = curfile;
847     (void) strcpy(save, fname);
848     f = fopen(save, "r");
849     if (f == NULL) {
850         loading = 0;
851         strcpy(curfile, save);
852         return SC_READFILE_DOESNTEXIST;
853     } /* */
854 
855     if (eraseflg) erasedb();
856 
857     while (! brokenpipe && fgets(line, sizeof(line), f)) {
858         linelim = 0;
859         if (line[0] != '#') (void) yyparse();
860     }
861     fclose(f);
862 
863     loading = 0;
864     linelim = -1;
865     if (eraseflg) {
866         cellassign = 0;
867     }
868     strcpy(curfile, save);
869     EvalAll();
870     modflg = 0;
871     return SC_READFILE_SUCCESS;
872 }
873 
874 /**
875  * \brief Expand a ~ in path to the user's home directory
876  *
877  * \param[in] path
878  *
879  * \return path
880  */
findhome(char * path)881 char * findhome(char * path) {
882     static char * HomeDir = NULL;
883 
884     if (* path == '~') {
885         char * pathptr;
886         char tmppath[PATHLEN];
887 
888         if (HomeDir == NULL) {
889             HomeDir = getenv("HOME");
890             if (HomeDir == NULL)
891                 HomeDir = "/";
892         }
893         pathptr = path + 1;
894         if ((* pathptr == '/') || (* pathptr == '\0'))
895             strcpy(tmppath, HomeDir);
896         else {
897             struct passwd * pwent;
898             char * namep;
899             char name[50];
900 
901             namep = name;
902             while ((*pathptr != '\0') && (*pathptr != '/'))
903                 *(namep++) = *(pathptr++);
904             *namep = '\0';
905             if ((pwent = getpwnam(name)) == NULL) {
906                 (void) sprintf(path, "Can't find user %s", name);
907                 return (NULL);
908             }
909             strcpy(tmppath, pwent->pw_dir);
910         }
911         strcat(tmppath, pathptr);
912         strcpy(path, tmppath);
913     }
914     return (path);
915 }
916 
917 /**
918  * \brief Open the input or output file
919  *
920  * \details Open the input or output file, setting up a pipe if needed.
921  *
922  * \param[in] fname file name
923  * \param[in] rpid
924  * \param[in] rfd
925  *
926  * \return file pointer
927  */
openfile(char * fname,int * rpid,int * rfd)928 FILE * openfile(char *fname, int *rpid, int *rfd) {
929     int pipefd[4];
930     int pid;
931     FILE *f;
932     char *efname;
933 
934     while (*fname && (*fname == ' '))    // Skip leading blanks
935         fname++;
936 
937     if (*fname != '|') {                 // Open file if not pipe
938         *rpid = 0;
939         if (rfd != NULL)
940             *rfd = 1;                    // Set to stdout just in case
941 
942         efname = findhome(fname);
943         return (fopen(efname, rfd == NULL ? "w" : "r"));
944     }
945 
946     fname++;                             // Skip |
947     efname = findhome(fname);
948     if (pipe(pipefd) < 0 || (rfd != NULL && pipe(pipefd+2) < 0)) {
949         sc_error("Can't make pipe to child");
950         *rpid = 0;
951         return (0);
952     }
953 
954     //deraw(rfd==NULL);
955 
956     if ((pid=fork()) == 0) {             // if child
957         (void) close(0);                 // close stdin
958         (void) close(pipefd[1]);
959         (void) dup(pipefd[0]);           // connect to first pipe
960         if (rfd != NULL) {               // if opening for read
961             (void) close(1);             // close stdout
962             (void) close(pipefd[2]);
963             (void) dup(pipefd[3]);       // connect to second pipe
964         }
965         (void) signal(SIGINT, SIG_DFL);  // reset
966         execl("/bin/sh", "sh", "-c", efname, 0, (char *) NULL);
967         exit (-127);
968     } else {                             // else parent
969         *rpid = pid;
970         if ((f = fdopen(pipefd[(rfd==NULL?1:2)], rfd==NULL?"w":"r")) == NULL) {
971             (void) kill(pid, 9);
972             sc_error("Can't fdopen %sput", rfd==NULL?"out":"in");
973             (void) close(pipefd[1]);
974             if (rfd != NULL)
975                 (void) close(pipefd[3]);
976             *rpid = 0;
977             return (0);
978         }
979     }
980     (void) close(pipefd[0]);
981     if (rfd != NULL) {
982         (void) close(pipefd[3]);
983         *rfd = pipefd[1];
984     }
985     return (f);
986 }
987 
988 // close a file opened by openfile(), if process wait for return
989 /**
990  * \brief Close a file opened by openfile()
991  *
992  * \details Close a file opened by openfile(). If process, wait for return
993  *
994  * \param[in] f file pointer
995  * \param[in] pid
996  * \param[in] rfd
997  *
998  * \return none
999  */
closefile(FILE * f,int pid,int rfd)1000 void closefile(FILE *f, int pid, int rfd) {
1001     int temp;
1002     wint_t wi;
1003 
1004     (void) fclose(f);
1005     if (pid) {
1006         while (pid != wait(&temp)) //;
1007         if (rfd==0) {
1008             printf("Press any key to continue ");
1009             fflush(stdout);
1010 #ifdef NCURSES
1011             cbreak();
1012 #endif
1013             ui_getch_b(&wi);
1014         } else {
1015             close(rfd);
1016 #ifdef NCURSES
1017             if (! get_conf_int("nocurses")) {
1018                 cbreak();
1019                 nonl();
1020                 noecho ();
1021             }
1022 #endif
1023         }
1024     }
1025     if (brokenpipe) {
1026         sc_error("Broken pipe");
1027         brokenpipe = FALSE;
1028     }
1029 }
1030 
1031 /**
1032  * \brief TODO <brief function description>
1033  *
1034  * \param[in] f file pointer
1035  *
1036  * \return none
1037  */
print_options(FILE * f)1038 void print_options(FILE *f) {
1039     if (
1040             ! optimize &&
1041             ! rndtoeven &&
1042             calc_order == BYROWS &&
1043             prescale == 1.0 &&
1044             ! get_conf_int("external_functions") &&
1045             tbl_style == 0
1046        )
1047         return; // No reason to do this
1048 
1049     (void) fprintf(f, "set");
1050     if (optimize)              (void) fprintf(f," optimize");
1051     if (rndtoeven)             (void) fprintf(f, " rndtoeven");
1052     if (calc_order != BYROWS ) (void) fprintf(f, " bycols");
1053     if (prescale != 1.0)       (void) fprintf(f, " prescale");
1054     if ( get_conf_int("external_functions") ) (void) fprintf(f, " external_functions");
1055     if (tbl_style)             (void) fprintf(f, " tblstyle = %s", tbl_style == TBL ? "tbl" : tbl_style == LATEX ? "latex" : tbl_style == SLATEX ? "slatex" : tbl_style == TEX ? "tex" : tbl_style == FRAME ? "frame" : "0" );
1056     (void) fprintf(f, "\n");
1057 }
1058 
1059 
1060 /**
1061  * \brief Import csv to sc
1062  *
1063  * \param[in] fname file name
1064  * \param[in] d = delim character
1065  *
1066  * \return 0 on success; -1 on error
1067  */
import_csv(char * fname,char d)1068 int import_csv(char * fname, char d) {
1069     register FILE * f;
1070     int r = 0, c = 0, cf = 0;
1071     wchar_t line_interp[FBUFLEN] = L"";
1072     char * token;
1073 
1074     int quote = 0; // if value has '"'. ex: 12,"1234,450.00",56
1075     char delim[2] = ""; //strtok receives a char *, not a char
1076     add_char(delim, d, 0);
1077 
1078     if ((f = fopen(fname , "r")) == NULL) {
1079         sc_error("Can't read file \"%s\"", fname);
1080         return -1;
1081     }
1082 
1083     // Check max length of line
1084     int max = max_length(f) + 1;
1085     if (max == 0) {
1086         sc_error("Can't read file \"%s\"", fname);
1087         return -1;
1088     }
1089     char line_in[max];
1090     rewind(f);
1091 
1092     // Check the numbers of lines in file
1093     int max_lines = count_lines(f);
1094     rewind(f);
1095 
1096     int i=0;
1097 
1098     // handle ","
1099     char lookf[4], repls[2], replb[2];
1100     sprintf(lookf, "\"%c\"", d);
1101     sprintf(repls, "%c", 6);
1102     sprintf(replb, "%c", d);
1103 
1104     // CSV file traversing
1105     while ( ! feof(f) && (fgets(line_in, sizeof(line_in), f) != NULL) ) {
1106         // show file loading progress
1107         i++; // increase number of line;
1108         if (i % 10 == 0 ) sc_info("loading line %d of %d", i, max_lines);
1109 
1110         // this hack is for importing file that have DOS eol
1111         int l = strlen(line_in);
1112         while (l--)
1113             if (line_in[l] == 0x0d) {
1114                 line_in[l] = '\0';
1115                 break;
1116             }
1117 
1118         strcpy(line_in, str_replace(line_in, lookf, repls)); // handle "," case
1119 
1120         // Split string using the delimiter
1121         token = xstrtok(line_in, delim);
1122         c = 0;
1123 
1124         while( token != NULL ) {
1125             if (r > MAXROWS - GROWAMT - 1 || c > ABSMAXCOLS - 1) break;
1126             clean_carrier(token);
1127             if ( token[0] == '\"' && token[strlen(token)-1] == '\"') {
1128                 quote = 1;
1129             } else if ( (token[0] == '\"' || quote) && strlen(token) && (token[strlen(token)-1] != '\"' || strlen(token) == 1) ) {
1130                 quote = 1;
1131                 char * next = xstrtok(NULL, delim);
1132 
1133                 if (next != NULL) {
1134                     sprintf(token + strlen(token), "%c%s", d, next);
1135                     continue;
1136                 }
1137             }
1138             if (quote) { // Remove quotes
1139                 del_char(token, 0);
1140                 del_char(token, strlen(token)-1);
1141             }
1142 
1143             char * st = str_replace (token, repls, replb); // handle "," case
1144 
1145             // number import
1146             if (strlen(st) && isnumeric(st) && ! get_conf_int("import_delimited_as_text")
1147             ) {
1148                 //wide char
1149                 swprintf(line_interp, BUFFERSIZE, L"let %s%d=%s", coltoa(c), r, st);
1150 
1151             // text import
1152             } else if (strlen(st)){
1153                 //wide char
1154                 swprintf(line_interp, BUFFERSIZE, L"label %s%d=\"%s\"", coltoa(c), r, st);
1155             }
1156             //wide char
1157             if (strlen(st)) send_to_interp(line_interp);
1158 
1159             if (++c > cf) cf = c;
1160             quote = 0;
1161             token = xstrtok(NULL, delim);
1162             free(st);
1163         }
1164 
1165         r++;
1166         if (r > MAXROWS - GROWAMT - 1 || c > ABSMAXCOLS - 1) break;
1167     }
1168     maxrow = r-1;
1169     maxcol = cf-1;
1170 
1171     auto_justify(0, maxcols, DEFWIDTH);
1172 
1173     fclose(f);
1174 
1175     EvalAll();
1176     return 0;
1177 }
1178 
1179 /**
1180  * \brief Import Markdown to sc
1181  *
1182  * \param[in] fname file name
1183  * \param[in] d
1184  *
1185  * \return 0 on success; -1 on error
1186  */
1187 
import_markdown(char * fname)1188 int import_markdown(char * fname) {
1189   register FILE * f;
1190   int r = 0, c = 0, cf = 0;
1191   wchar_t line_interp[FBUFLEN] = L"";
1192   wchar_t line_interp_align[FBUFLEN] = L"";
1193   char * token;
1194 
1195   //int pipe = 0; // if value has '"'. ex: 12,"1234,450.00",56
1196   int rownr = 0;
1197   char d = '|';
1198   char delim[2] = ""; //strtok receives a char *, not a char
1199   add_char(delim, d, 0);
1200   //    int linenumber = 0;
1201 
1202   if ((f = fopen(fname , "r")) == NULL) {
1203     sc_error("Can't read file \"%s\"", fname);
1204     return -1;
1205   }
1206 
1207   // Check max length of line
1208   int max = max_length(f) + 1;
1209   if (max == 0) {
1210     sc_error("Can't read file \"%s\"", fname);
1211     return -1;
1212   }
1213   char line_in[max];
1214   char line_in_head[max];
1215   char align[max];
1216   rewind(f);
1217 
1218   while ( ! feof(f) && (fgets(line_in, sizeof(line_in), f) != NULL) ) {
1219 
1220     // this hack is for importing file that have DOS eol
1221     int l = strlen(line_in);
1222     while (l--){
1223       if (line_in[l] == 0x0d) {
1224         line_in[l] = '\0';
1225         break;
1226       }
1227     }
1228 
1229     /*
1230        if ( line_in[0] == '|' && line_in[strlen(line_in)-1] == '|') {
1231        pipe = 1;
1232        }
1233        */
1234     //pipe = 0;
1235     del_char(line_in, 0);
1236     del_char(line_in, strlen(line_in)-1);
1237 
1238     if(r==1){
1239       strcpy(line_in_head, line_in);
1240 
1241       token = xstrtok(line_in_head, delim);
1242       c = 0;
1243 
1244       while( token != NULL ) {
1245         if (r > MAXROWS - GROWAMT - 1 || c > ABSMAXCOLS - 1) break;
1246         clean_carrier(token);
1247         token = ltrim(token, ' ');
1248         token = rtrim(token, ' ');
1249 
1250         if((token[0] == ':' && token[strlen(token)-1] == '-') ||
1251             (token[0] == '-' && token[strlen(token)-1] == '-')){
1252           align[c] = 'l';
1253           swprintf(line_interp_align, BUFFERSIZE, L"leftjustify %s", v_name(r-1, c));
1254 
1255         }
1256         else if(token[0] == '-' && token[strlen(token)-1] == ':'){
1257           align[c] = 'r';
1258           swprintf(line_interp_align, BUFFERSIZE, L"rightjustify %s", v_name(r-1, c));
1259         }
1260         else{
1261           swprintf(line_interp_align, BUFFERSIZE, L"center %s", v_name(r-1, c));
1262           align[c] = 'c';
1263         }
1264 
1265         send_to_interp(line_interp_align);
1266         token = xstrtok(NULL, delim);
1267         c++;
1268       }
1269     }
1270     else{
1271 
1272       // Split string using the delimiter
1273       token = xstrtok(line_in, delim);
1274 
1275       c = 0;
1276 
1277       while( token != NULL ) {
1278         if (r > MAXROWS - GROWAMT - 1 || c > ABSMAXCOLS - 1) break;
1279 
1280         if(r == 0){
1281           rownr = r;
1282         }
1283         else{
1284           rownr = r-1;
1285         }
1286 
1287         clean_carrier(token);
1288         token = ltrim(token, ' ');
1289         token = rtrim(token, ' ');
1290 
1291         char * st = str_replace(token, "\"", "''"); //replace double quotes inside string
1292 
1293         // number import
1294         if (isnumeric(st) && strlen(st) && ! atoi(get_conf_value("import_delimited_as_text"))) {
1295           //wide char
1296           swprintf(line_interp, BUFFERSIZE, L"let %s%d=%s", coltoa(c), rownr, st);
1297 
1298           // text import
1299         } else if (strlen(st)){
1300           //wide char
1301           swprintf(line_interp, BUFFERSIZE, L"label %s%d=\"%s\"", coltoa(c), rownr, st);
1302         }
1303         //wide char
1304         if (strlen(st)){
1305           send_to_interp(line_interp);
1306 
1307           if(r>0){
1308             if(align[c] == 'l'){
1309               swprintf(line_interp_align, BUFFERSIZE, L"leftjustify %s", v_name(rownr, c));
1310             }
1311             else if(align[c] == 'r'){
1312               swprintf(line_interp_align, BUFFERSIZE, L"rightjustify %s", v_name(rownr, c));
1313             }
1314             else{
1315               swprintf(line_interp_align, BUFFERSIZE, L"center %s", v_name(rownr, c));
1316             }
1317             send_to_interp(line_interp_align);
1318           }
1319 
1320         }
1321         free(st);
1322 
1323         if (++c > cf) cf = c;
1324         token = xstrtok(NULL, delim);
1325       }
1326     }
1327 
1328     maxcol = cf-1;
1329     r++;
1330     if (r > MAXROWS - GROWAMT - 1 || c > ABSMAXCOLS - 1) break;
1331   }
1332   maxrow = r-1;
1333   maxcol = cf-1;
1334 
1335   auto_justify(0, maxcols, DEFWIDTH);
1336 
1337   fclose(f);
1338 
1339   EvalAll();
1340   return 0;
1341 }
1342 
1343 
1344 
1345 /**
1346  * \brief Export to CSV, TAB, or plain TXT
1347  *
1348  * \param[in] r0
1349  * \param[in] c0
1350  * \param[in] rn
1351  * \param[in] cn
1352  *
1353  * \return none
1354  */
do_export(int r0,int c0,int rn,int cn)1355 void do_export(int r0, int c0, int rn, int cn) {
1356     int force_rewrite = 0;
1357     char type_export[4] = "";
1358     char ruta[PATHLEN];
1359     char linea[BUFFERSIZE];
1360 
1361     if (inputline[1] == L'!') force_rewrite = 1;
1362     wcstombs(linea, inputline, BUFFERSIZE); // Use new variable to keep command history untouched
1363     del_range_chars(linea, 0, 1 + force_rewrite); // Remove 'e' or 'e!' from inputline
1364 
1365     // Get format to export
1366     if (str_in_str(linea, "csv") == 0) {
1367         strcpy(type_export, "csv");
1368     } else if (str_in_str(linea, "tab") == 0) {
1369         strcpy(type_export, "tab");
1370     } else if (str_in_str(linea, "tex") == 0) {
1371         strcpy(type_export, "tex");
1372     } else if (str_in_str(linea, "txt") == 0) {
1373         strcpy(type_export, "txt");
1374     } else if (str_in_str(linea, "mkd") == 0) {
1375         strcpy(type_export, "mkd");
1376     }
1377 
1378     // Get route and file name to write.
1379     // Use parameter if any.
1380     if (strlen(linea) > 4) {   // ex. 'csv '
1381         del_range_chars(linea, 0, 3); // remove 'csv'
1382         strcpy(ruta, linea);
1383 
1384     // Use curfile name and '.csv' o '.tab' extension
1385     // Remove current '.sc' extension if necessary
1386     } else if (curfile[0]) {
1387         strcpy(ruta, curfile);
1388         char * ext = strrchr(ruta, '.');
1389         if (ext != NULL) del_range_chars(ruta, strlen(ruta) - strlen(ext), strlen(ruta)-1);
1390         sprintf(ruta + strlen(ruta), ".%s", type_export);
1391 
1392     } else {
1393         sc_error("No filename specified !");
1394         return;
1395     }
1396 
1397     if (! force_rewrite && file_exists(ruta) && strlen(ruta) > 0) {
1398         sc_error("File %s already exists. Use \"!\" to force rewrite.", ruta);
1399         return;
1400     }
1401 
1402     #ifdef AUTOBACKUP
1403     // check if backup of fname exists.
1404     // if it exists and '!' is set, remove it.
1405     // if it exists and curfile = fname, remove it.
1406     // else return.
1407     if (( !strcmp(type_export, "csv") || !strcmp(type_export, "tab")) && (strlen(ruta) && backup_exists(ruta))) {
1408         if (force_rewrite || (strlen(curfile) && !strcmp(curfile, ruta))) {
1409             remove_backup(ruta);
1410         } else {
1411             sc_error("Backup file of %s exists. Use \"!\" to force the write process.", ruta);
1412             return;
1413         }
1414     }
1415     #endif
1416 
1417     // Call export routines
1418     if (strcmp(type_export, "csv") == 0) {
1419         export_delim(ruta, get_delim("csv"), r0, c0, rn, cn, 1);
1420     } else if (strcmp(type_export, "tab") == 0) {
1421         export_delim(ruta, '\t', r0, c0, rn, cn, 1);
1422     } else if (strcmp(type_export, "tex") == 0) {
1423         export_latex(ruta, r0, c0, rn, cn, 1);
1424     } else if (strcmp(type_export, "txt") == 0) {
1425         export_plain(ruta, r0, c0, rn, cn);
1426     } else if (strcmp(type_export, "mkd") == 0) {
1427         export_markdown(ruta, r0, c0, rn, cn);
1428     }
1429 }
1430 
1431 
1432 /**
1433  * \brief Export to md file with markdown table
1434  *
1435  * \param[in] fname file name
1436  * \param[in] r0
1437  * \param[in] c0
1438  * \param[in] rn
1439  * \param[in] cn
1440  *
1441  * \return none
1442  */
export_markdown(char * fname,int r0,int c0,int rn,int cn)1443 void export_markdown(char * fname, int r0, int c0, int rn, int cn) {
1444     FILE * f;
1445     int row, col;
1446     register struct ent ** pp;
1447     int pid;
1448     wchar_t out[FBUFLEN] = L"";
1449 
1450     if (fname == NULL)
1451         f = stdout;
1452     else {
1453         sc_info("Writing file \"%s\"...", fname);
1454         if ((f = openfile(fname, &pid, NULL)) == (FILE *)0) {
1455             sc_error ("Can't create file \"%s\"", fname);
1456             return;
1457         }
1458     }
1459 
1460     // to prevent empty lines at the end of the file
1461     struct ent * ent = go_end();
1462     if (rn > ent->row) rn = ent->row;
1463     ent = goto_last_col(); // idem with columns
1464     if (cn > ent->col) cn = ent->col;
1465 
1466     char num [FBUFLEN] = "";
1467     char text[FBUFLEN] = "";
1468     char formated_s[FBUFLEN] = "";
1469     char dashline[FBUFLEN] = "";
1470     int res = -1;
1471     int align = 1;
1472     int dash_num;
1473     int rowfmt;
1474 
1475     for (row = r0; row <= rn; row++) {
1476         for (rowfmt=0; rowfmt<row_format[row]; rowfmt++) {
1477 
1478             // ignore hidden rows
1479             //if (row_hidden[row]) continue;
1480 
1481             for (pp = ATBL(tbl, row, col = c0); col <= cn; col++, pp++) {
1482                 // ignore hidden cols
1483                 //if (col_hidden[col]) continue;
1484 
1485                 if (col == c0) {
1486                     (void) fprintf (f, "| ");
1487                 } else if (col <= cn) {
1488                     (void) fprintf (f, " | ");
1489                 }
1490 
1491                 //make header border of dashes with alignment characters
1492                 if (row == 0) {
1493                     if (col == c0) strcat (dashline, "|");
1494                     if (align == 0) {
1495                         strcat (dashline, ":");
1496                     } else {
1497                         strcat (dashline, "-");
1498                     }
1499                     for (dash_num = 0; dash_num < fwidth[col]; dash_num++) {
1500                         strcat (dashline, "-");
1501                     }
1502                     if(align >= 0) {
1503                         strcat (dashline, ":");
1504                     } else {
1505                         strcat (dashline, "-");
1506                     }
1507                     strcat (dashline, "|");
1508                 }
1509 
1510                 if (*pp) {
1511                     num [0] = '\0';
1512                     text[0] = '\0';
1513                     out [0] = L'\0';
1514                     formated_s[0] = '\0';
1515                     res = -1;
1516                     align = 1;
1517 
1518                     // If a numeric value exists
1519                     if ( (*pp)->flags & is_valid) {
1520                         res = ui_get_formated_value(pp, col, formated_s);
1521                         // res = 0, indicates that in num we store a date
1522                         // res = 1, indicates a format is applied in num
1523                         if (res == 0 || res == 1) {
1524                             strcpy(num, formated_s);
1525                         } else if (res == -1) {
1526                             sprintf(num, "%.*f", precision[col], (*pp)->v);
1527                         }
1528                     }
1529 
1530                     // If a string exists
1531                     if ((*pp)->label) {
1532                         strcpy(text, (*pp)->label);
1533                         align = 1;                                // right alignment
1534                         if ((*pp)->flags & is_label) {            // center alignment
1535                             align = 0;
1536                         } else if ((*pp)->flags & is_leftflush) { // left alignment
1537                             align = -1;
1538                         } else if (res == 0) {                    // res must ¿NOT? be zero for label to be printed
1539                             text[0] = '\0';
1540                         }
1541                     }
1542 
1543 
1544                     pad_and_align (text, num, fwidth[col], align, 0, out, row_format[row]);
1545 
1546                     wchar_t new[wcslen(out)+1];
1547                     wcscpy(new, out);
1548                     int cw = count_width_widestring(new, fwidth[col]);
1549 
1550                     if (wcslen(new) > cw && rowfmt) {
1551                         int count_row = 0;
1552                         for (count_row = 0; count_row < rowfmt; count_row++) {
1553                             cw = count_width_widestring(new, fwidth[col]);
1554                             if (cw) del_range_wchars(new, 0, cw-1);
1555                             int whites = fwidth[col] - wcslen(new);
1556                             while (whites-- > 0) add_wchar(new, L' ', wcslen(new));
1557                         }
1558                         new[cw] = L'\0';
1559                         fprintf (f, "%ls", new);
1560                     } else if (! rowfmt && wcslen(new)) {
1561                         if (get_conf_int("truncate") || !get_conf_int("overlap")) new[cw] = L'\0';
1562                         fprintf (f, "%ls", new);
1563                     } else {
1564                         fprintf (f, "%*s", fwidth[col], " ");
1565                     }
1566                 } else {
1567                     fprintf (f, "%*s", fwidth[col], " ");
1568                 }
1569             }
1570             fprintf(f," |\n");
1571         }
1572 
1573         if (row == 0) (void) fprintf(f,"%s\n",dashline);
1574     }
1575 
1576     if (fname != NULL) {
1577         closefile(f, pid, 0);
1578         if (! pid) {
1579             sc_info("File \"%s\" written", fname);
1580         }
1581     }
1582 }
1583 
1584 /**
1585  * \brief Export to plain TXT
1586  *
1587  * \param[in] fname file name
1588  * \param[in] r0
1589  * \param[in] c0
1590  * \param[in] rn
1591  * \param[in] cn
1592  *
1593  * \return none
1594  */
export_plain(char * fname,int r0,int c0,int rn,int cn)1595 void export_plain(char * fname, int r0, int c0, int rn, int cn) {
1596     FILE * f;
1597     int row, col;
1598     register struct ent ** pp;
1599     int pid;
1600     wchar_t out[FBUFLEN] = L"";
1601 
1602     if (fname == NULL)
1603         f = stdout;
1604     else {
1605         sc_info("Writing file \"%s\"...", fname);
1606         if ((f = openfile(fname, &pid, NULL)) == (FILE *)0) {
1607             sc_error ("Can't create file \"%s\"", fname);
1608             return;
1609         }
1610     }
1611 
1612     // to prevent empty lines at the end of the file
1613     struct ent * ent = go_end();
1614     if (rn > ent->row) rn = ent->row;
1615     ent = goto_last_col(); // idem with columns
1616     if (cn > ent->col) cn = ent->col;
1617 
1618     char num [FBUFLEN] = "";
1619     char text[FBUFLEN] = "";
1620     char formated_s[FBUFLEN] = "";
1621     int res = -1;
1622     int align = 1;
1623     int rowfmt;
1624 
1625     for (row = r0; row <= rn; row++) {
1626         for (rowfmt=0; rowfmt<row_format[row]; rowfmt++) {
1627 
1628             // ignore hidden rows
1629             //if (row_hidden[row]) continue;
1630 
1631             for (pp = ATBL(tbl, row, col = c0); col <= cn; col++, pp++) {
1632                 // ignore hidden cols
1633                 //if (col_hidden[col]) continue;
1634 
1635                 if (*pp) {
1636 
1637                     num [0] = '\0';
1638                     text[0] = '\0';
1639                     out [0] = L'\0';
1640                     formated_s[0] = '\0';
1641                     res = -1;
1642                     align = 1;
1643 
1644                     // If a numeric value exists
1645                     if ( (*pp)->flags & is_valid) {
1646                         res = ui_get_formated_value(pp, col, formated_s);
1647                         // res = 0, indicates that in num we store a date
1648                         // res = 1, indicates a format is applied in num
1649                         if (res == 0 || res == 1) {
1650                             strcpy(num, formated_s);
1651                         } else if (res == -1) {
1652                             sprintf(num, "%.*f", precision[col], (*pp)->v);
1653                         }
1654                     }
1655 
1656                     // If a string exists
1657                     if ((*pp)->label) {
1658                         strcpy(text, (*pp)->label);
1659                         align = 1;                                // right alignment
1660                         if ((*pp)->flags & is_label) {            // center alignment
1661                             align = 0;
1662                         } else if ((*pp)->flags & is_leftflush) { // left alignment
1663                             align = -1;
1664                         } else if (res == 0) {                    // res must ¿NOT? be zero for label to be printed
1665                             text[0] = '\0';
1666                         }
1667                     }
1668 
1669                     pad_and_align (text, num, fwidth[col], align, 0, out, row_format[row]);
1670 
1671                     wchar_t new[wcslen(out)+1];
1672                     wcscpy(new, out);
1673                     int cw = count_width_widestring(new, fwidth[col]);
1674 
1675                     if (wcslen(new) > cw && rowfmt) {
1676                         int count_row = 0;
1677                         for (count_row = 0; count_row < rowfmt; count_row++) {
1678                             cw = count_width_widestring(new, fwidth[col]);
1679                             if (cw) del_range_wchars(new, 0, cw-1);
1680                             int whites = fwidth[col] - wcslen(new);
1681                             while (whites-- > 0) add_wchar(new, L' ', wcslen(new));
1682                         }
1683                         new[cw] = L'\0';
1684                         fprintf (f, "%ls", new);
1685                     } else if (! rowfmt && wcslen(new)) {
1686                         if (get_conf_int("truncate") || !get_conf_int("overlap")) new[cw] = L'\0';
1687                         fprintf (f, "%ls", new);
1688                     } else {
1689                         fprintf (f, "%*s", fwidth[col], " ");
1690                     }
1691                 } else {
1692                     fprintf (f, "%*s", fwidth[col], " ");
1693                 }
1694             }
1695             fprintf(f,"\n");
1696         }
1697     }
1698     if (fname != NULL) {
1699         closefile(f, pid, 0);
1700         if (! pid) {
1701             sc_info("File \"%s\" written", fname);
1702         }
1703     }
1704 
1705 }
1706 
export_latex(char * fname,int r0,int c0,int rn,int cn,int verbose)1707 void export_latex(char * fname, int r0, int c0, int rn, int cn, int verbose) {
1708     FILE * f;
1709     int row, col;
1710     register struct ent ** pp;
1711     int pid;
1712 
1713     // to prevent empty lines at the end of the file
1714     struct ent * ent = go_end();
1715     if (rn > ent->row) rn = ent->row;
1716     ent = goto_last_col(); // idem with columns
1717     if (cn > ent->col) cn = ent->col;
1718 
1719     if (verbose) sc_info("Writing file \"%s\"...", fname);
1720 
1721     if (fname == NULL)
1722         f = stdout;
1723     else {
1724         if ((f = openfile(fname, &pid, NULL)) == (FILE *)0) {
1725             if (verbose) sc_error ("Can't create file \"%s\"", fname);
1726             return;
1727         }
1728     }
1729 
1730     // do the stuff
1731     fprintf(f,"%% ** SC-IM spreadsheet output\n\\begin{tabular}{");
1732     for (col=c0;col<=cn; col++) fprintf(f,"c");
1733     fprintf(f, "}\n");
1734     char coldelim = '&';
1735     for (row=r0; row<=rn; row++) {
1736         for (pp = ATBL(tbl, row, col=c0); col<=cn; col++, pp++) {
1737             if (*pp) {
1738                 char *s;
1739                 if ((*pp)->flags & is_valid) {
1740                     if ((*pp)->cellerror) {
1741                         (void) fprintf (f, "%*s", fwidth[col], ((*pp)->cellerror == CELLERROR ? "ERROR" : "INVALID"));
1742                     } else if ((*pp)->format) {
1743                         char field[FBUFLEN];
1744                         if (*((*pp)->format) == ctl('d')) {
1745                             time_t v = (time_t) ((*pp)->v);
1746                             strftime(field, sizeof(field), ((*pp)->format)+1, localtime(&v));
1747                         } else
1748                             format((*pp)->format, precision[col], (*pp)->v, field, sizeof(field));
1749                         unspecial(f, field, coldelim);
1750                     } else {
1751                         char field[FBUFLEN];
1752                         (void) engformat(realfmt[col], fwidth[col], precision[col], (*pp) -> v, field, sizeof(field));
1753                         unspecial(f, field, coldelim);
1754                     }
1755                 }
1756                 if ((s = (*pp)->label)) unspecial(f, s, coldelim);
1757             }
1758                 if (col < cn) fprintf(f,"%c", coldelim);
1759         }
1760         if (row < rn) (void) fprintf (f, "\\\\");
1761         fprintf(f,"\n");
1762     }
1763 
1764     fprintf(f,"\\end{tabular}\n%% ** end of SC-IM spreadsheet output\n");
1765 
1766     if (fname != NULL) closefile(f, pid, 0);
1767 
1768     if (! pid && verbose) sc_info("File \"%s\" written", fname);
1769 }
1770 
1771 /**
1772  * \brief TODO Document unspecial()
1773  *
1774  * \details Unspecial (backquotes - > ") things that are special
1775  * chars in a table
1776  *
1777  * \param[in] f file pointer
1778  * \param[in] srt string pointer
1779  * \param[in] delim
1780  *
1781  * \return none
1782  */
unspecial(FILE * f,char * str,int delim)1783 void unspecial(FILE * f, char * str, int delim) {
1784     int backquote = 0;
1785 
1786     if (str_in_str(str, ",") != -1) backquote = 1;
1787     if (backquote) putc('\"', f);
1788     if (*str == '\\') str++; // delete wheeling string operator, OK?
1789     while (*str) {
1790         // for LATEX export
1791         if (delim == '&' && ( (*str == '&') || (*str == '$') ||
1792            (*str == '#') || (*str == '%') || (*str == '{') || (*str == '}') || (*str == '&')))
1793            putc('\\', f);
1794         putc(*str, f);
1795         str++;
1796     }
1797     if (backquote) putc('\"', f);
1798 }
1799 
1800 /**
1801  * \brief TODO Document export_delim
1802  *
1803  * \param[in] fname full path of the file
1804  * \param[in] coldelim
1805  * \param[in] r0
1806  * \param[in] c0
1807  * \param[in] rn
1808  * \param[in] cn
1809  * \param[in] verbose
1810  *
1811  * \return none
1812  */
export_delim(char * fname,char coldelim,int r0,int c0,int rn,int cn,int verbose)1813 void export_delim(char * fname, char coldelim, int r0, int c0, int rn, int cn, int verbose) {
1814     FILE * f;
1815     int row, col;
1816     register struct ent ** pp;
1817     int pid;
1818 
1819     // to prevent empty lines at the end of the file
1820     struct ent * ent = go_end();
1821     if (rn > ent->row) rn = ent->row;
1822     ent = goto_last_col(); // idem with columns
1823     if (cn > ent->col) cn = ent->col;
1824 
1825     if (verbose) sc_info("Writing file \"%s\"...", fname);
1826 
1827     if (fname == NULL)
1828         f = stdout;
1829     else {
1830         if ((f = openfile(fname, &pid, NULL)) == (FILE *)0) {
1831             if (verbose) sc_error ("Can't create file \"%s\"", fname);
1832             return;
1833         }
1834     }
1835 
1836     for (row = r0; row <= rn; row++) {
1837         for (pp = ATBL(tbl, row, col = c0); col <= cn; col++, pp++) {
1838             int last_valid_col = right_limit(row)->col; // for issue #374
1839             if (col > last_valid_col) continue;
1840             if (*pp) {
1841                 char * s;
1842                 if ((*pp)->flags & is_valid) {
1843                     if ((*pp)->cellerror) {
1844                         (void) fprintf (f, "%*s", fwidth[col], ((*pp)->cellerror == CELLERROR ? "ERROR" : "INVALID"));
1845                     } else if ((*pp)->format) {
1846                         char field[FBUFLEN];
1847                         if (*((*pp)->format) == 'd') {  // Date format
1848                             time_t v = (time_t) ((*pp)->v);
1849                             strftime(field, sizeof(field), ((*pp)->format)+1, localtime(&v));
1850                         } else {                        // Numeric format
1851                             format((*pp)->format, precision[col], (*pp)->v, field, sizeof(field));
1852                         }
1853                         ltrim(field, ' ');
1854                         unspecial(f, field, coldelim);
1855                     } else { //eng number format
1856                         char field[FBUFLEN] = "";
1857                         (void) engformat(realfmt[col], fwidth[col], precision[col], (*pp)->v, field, sizeof(field));
1858                         ltrim(field, ' ');
1859                         unspecial(f, field, coldelim);
1860                     }
1861                 }
1862                 if ((s = (*pp)->label)) {
1863                     ltrim(s, ' ');
1864                     unspecial(f, s, coldelim);
1865                 }
1866             }
1867             if (col < cn && col < last_valid_col)
1868                 (void) fprintf(f,"%c", coldelim);
1869         }
1870         (void) fprintf(f,"\n");
1871     }
1872     if (fname != NULL)
1873         closefile(f, pid, 0);
1874 
1875     if (! pid && verbose) {
1876         sc_info("File \"%s\" written", fname);
1877     }
1878 }
1879 
1880 /**
1881  * \brief Check what is the max length of all the lines in a file
1882  *
1883  * \details Check the maximum length of lines in a file. Note:
1884  * FILE * f shall be opened.
1885  *
1886  * \param[in] f file pointer
1887  * \return file length + 1
1888  */
max_length(FILE * f)1889 int max_length(FILE * f) {
1890     if (f == NULL) return -1;
1891     int count = 0, max = 0;
1892     int c = fgetc(f);
1893 
1894     while (c != EOF) {
1895         if (c != '\n') {
1896             count++;
1897         } else if (count > max) {
1898             max = count;
1899             count = 0;
1900         } else {
1901             count = 0;
1902         }
1903         c = fgetc(f);
1904     }
1905     return max + 1;
1906 }
1907 
1908 /**
1909  * \brief Check the number of lines of a file
1910  *
1911  * \details Check the numbers of lines of a file. it count \n chars.
1912  * FILE * f shall be opened.
1913  *
1914  * \param[in] f file pointer
1915  * \return number
1916  */
count_lines(FILE * f)1917 int count_lines(FILE * f) {
1918     int count = 0;
1919     if (f == NULL) return count;
1920     int c = fgetc(f);
1921 
1922     while (c != EOF) {
1923         if (c == '\n') count++;
1924         c = fgetc(f);
1925     }
1926     return count;
1927 }
1928 
1929 /**
1930  * \brief TODO Document plugin_exists()
1931  *
1932  * \param[in] name
1933  * \param[in] len
1934  * \param[in] path
1935  *
1936  * \return none
1937  */
plugin_exists(char * name,int len,char * path)1938 int plugin_exists(char * name, int len, char * path) {
1939     FILE * fp;
1940     static char * HomeDir;
1941     char cwd[1024];
1942 
1943     if (getcwd(cwd, sizeof(cwd)) != NULL) {
1944         strcpy((char *) path, cwd);
1945         strcat((char *) path, "/");
1946         strncat((char *) path, name, len);
1947         if ((fp = fopen((char *) path, "r"))) {
1948             fclose(fp);
1949             return 1;
1950         }
1951     }
1952     /* Check XDG_CONFIG_HOME */
1953     if ((HomeDir = getenv("XDG_CONFIG_HOME"))) {
1954         sprintf((char *) path, "%s/sc-im/%s", HomeDir, name);
1955         if ((fp = fopen((char *) path, "r"))) {
1956             fclose(fp);
1957             return 1;
1958         }
1959     }
1960     /* Check compile time path (default ~/.config/sc-im) */
1961     if ((HomeDir = getenv("HOME"))) {
1962         sprintf((char *) path, "%s/%s/%s", HomeDir, CONFIG_DIR, name);
1963         if ((fp = fopen((char *) path, "r"))) {
1964             fclose(fp);
1965             return 1;
1966         }
1967     }
1968     /* LEGACY PATH */
1969     if ((HomeDir = getenv("HOME"))) {
1970         sprintf((char *) path, "%s/.scim/%s", HomeDir, name);
1971         if ((fp = fopen((char *) path, "r"))) {
1972             fclose(fp);
1973             return 1;
1974         }
1975     }
1976     strcpy((char *) path, HELP_PATH);
1977     strcat((char *) path, "/");
1978     strncat((char *) path, name, len);
1979     if ((fp = fopen((char *) path, "r"))) {
1980         fclose(fp);
1981         return 1;
1982     }
1983     return 0;
1984 }
1985 
1986 /**
1987  * \brief TODO Document do_autobackup()
1988  * \return none
1989  */
do_autobackup()1990 void * do_autobackup() {
1991     int len = strlen(curfile);
1992     //if (loading || ! len) return (void *) -1;
1993     //if (! len || ! modflg) return (void *) -1;
1994     if (! len) return (void *) -1;
1995 
1996     char * pstr = strrchr(curfile, '/');
1997     int pos = pstr == NULL ? -1 : pstr - curfile;
1998     char name[PATHLEN] = {'\0'};
1999     char namenew[PATHLEN] = {'\0'};
2000     strcpy(name, curfile);
2001     add_char(name, '.', pos+1);
2002     sprintf(name + strlen(name), ".bak");
2003     sprintf(namenew, "%.*s.new", PATHLEN-5, name);
2004     //if (get_conf_int("debug")) sc_info("doing autobackup of file:%s", name);
2005 
2006     // create new version
2007     if (! strcmp(&name[strlen(name)-7], ".sc.bak")) {
2008         register FILE * f;
2009         if ((f = fopen(namenew , "w")) == NULL) return (void *) -1;
2010         write_fd(f, 0, 0, maxrow, maxcol);
2011         fclose(f);
2012     } else if (! strcmp(&name[strlen(name)-8], ".csv.bak")) {
2013         export_delim(namenew, get_delim("csv"), 1, 0, maxrow, maxcol, 0);
2014 #ifdef XLSX_EXPORT
2015     } else if (! strcmp(&name[strlen(name)-9], ".xlsx.bak")) {
2016         export_delim(namenew, ',', 0, 0, maxrow, maxcol, 0);
2017         export_xlsx(namenew, 0, 0, maxrow, maxcol);
2018 #endif
2019     } else if (! strcmp(&name[strlen(name)-8], ".tab.bak") || ! strcmp(&name[strlen(name)-8], ".tsv.bak")) {
2020         export_delim(namenew, '\t', 0, 0, maxrow, maxcol, 0);
2021     }
2022 
2023     // delete if exists name
2024     remove(name);
2025 
2026     // rename name.new to name
2027     rename(namenew, name);
2028 
2029     return (void *) 0;
2030 }
2031 
2032 /**
2033  * \brief Check if it is time to do an autobackup
2034  * \return none
2035  */
handle_backup()2036 void handle_backup() {
2037     #ifdef AUTOBACKUP
2038     extern struct timeval lastbackup_tv; // last backup timer
2039     extern struct timeval current_tv; //runtime timer
2040 
2041     int autobackup = get_conf_int("autobackup");
2042     if (autobackup && autobackup > 0 && (current_tv.tv_sec - lastbackup_tv.tv_sec > autobackup || (lastbackup_tv.tv_sec == 0 && lastbackup_tv.tv_usec == 0))) {
2043         #ifdef HAVE_PTHREAD
2044             if (pthread_exists) pthread_join (fthread, NULL);
2045             pthread_exists = (pthread_create(&fthread, NULL, do_autobackup, NULL) == 0) ? 1 : 0;
2046         #else
2047             do_autobackup();
2048         #endif
2049         gettimeofday(&lastbackup_tv, NULL);
2050     }
2051     #endif
2052     return;
2053 }
2054 
2055 /**
2056  * \brief Remove autobackup file
2057  * \details Remove autobackup file. Used when quitting or when loading
2058  * a new file.
2059  * \param[in] file file pointer
2060  * \return none
2061  */
remove_backup(char * file)2062 void remove_backup(char * file) {
2063     int len = strlen(file);
2064     if (!len) return;
2065     char * pstr = strrchr(file, '/');
2066     int pos = pstr == NULL ? -1 : pstr - file;
2067     char name[len+6];
2068     strcpy(name, file);
2069     add_char(name, '.', pos+1);
2070     sprintf(name + strlen(name), ".bak");
2071     remove(name);
2072     return;
2073 }
2074 
2075 /**
2076  * \brief TODO Document backup_exists()
2077  * \param[in] file file pointer
2078  * \return none
2079  */
backup_exists(char * file)2080 int backup_exists(char * file) {
2081     int len = strlen(file);
2082     if (!len) return 0;
2083     char * pstr = strrchr(file, '/');
2084     int pos = pstr == NULL ? -1 : pstr - file;
2085     char name[len+6];
2086     strcpy(name, file);
2087     add_char(name, '.', pos+1);
2088     sprintf(name + strlen(name), ".bak");
2089     FILE * fp;
2090     if ((fp = fopen((char *) name, "r"))) {
2091         fclose(fp);
2092         return 1;
2093     }
2094     return 0;
2095 }
2096 
2097 /**
2098  * \brief open file nested
2099  * \param[in] file name string
2100  * \return none
2101  */
openfile_nested(char * file)2102 void openfile_nested(char * file) {
2103     char * cmd = get_conf_value("default_open_file_under_cursor_cmd");
2104     if (cmd == NULL || ! strlen(cmd)) return;
2105     char syscmd[PATHLEN + strlen(cmd)];
2106     sprintf(syscmd, "%s", cmd);
2107     sprintf(syscmd + strlen(syscmd), " %s", file);
2108     system(syscmd);
2109 }
2110 
2111 /**
2112  * \brief open file under cursor
2113  * \param[in] current row and column
2114  * \return none
2115  */
openfile_under_cursor(int r,int c)2116 void openfile_under_cursor(int r, int c) {
2117     register struct ent ** pp;
2118     pp = ATBL(tbl, r, c);
2119     if (*pp && (*pp)->label) {
2120         char text[FBUFLEN] = "";
2121         strcpy(text, (*pp)->label);
2122         openfile_nested(text);
2123     }
2124 }
2125