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 xlsx.c
40  * \author Andrés Martinelli <andmarti@gmail.com>
41  * \date 2017-07-18
42  * \brief TODO Write a tbrief file description.
43  *
44  * \details xlsx import requires:
45  * - libzip-dev
46  * - libxml2-dev
47  *
48  * \details xlsx export requires
49  * - libxlsxwriter
50  */
51 
52 #include <stdio.h>
53 #include <string.h>
54 #include <errno.h>
55 #include <ctype.h>   // for isdigit
56 #include <stdlib.h>  // for atoi
57 #include "macros.h"
58 #include "sc.h"
59 #include "cmds.h"
60 #include "tui.h"
61 #include "conf.h"
62 #include "lex.h"
63 #include "utils/string.h"
64 
65 #ifdef XLSX
66 #include <zip.h>
67 #include <libxml/parser.h>
68 #include <libxml/tree.h>
69 #include "xlsx.h"
70 
71 /**
72  * \brief TODO Document get_xlsx_string()
73  *
74  * \details This function takes the DOM of the sharedStrings file
75  * and based on position, it returns the according string. Note
76  * that 0 is the first string.
77  *
78  * \param[in] doc
79  * \param[in] pos
80  *
81  * \return none
82  */
83 
get_xlsx_string(xmlDocPtr doc,int pos)84 char * get_xlsx_string(xmlDocPtr doc, int pos) {
85     xmlNode * cur_node = xmlDocGetRootElement(doc)->xmlChildrenNode;
86     xmlNode * father;
87     char * result = NULL;
88 
89     while (pos--) cur_node = cur_node->next;
90 
91     father = cur_node;
92     cur_node = father->xmlChildrenNode;
93 
94     while (father != NULL) {  // traverse children
95         while (cur_node != NULL) {  // traverse relatives
96             if ( ! xmlStrcmp(cur_node->name, (const xmlChar *) "t")
97                 && cur_node->xmlChildrenNode != NULL
98                 && cur_node->xmlChildrenNode->content != NULL
99                ) {
100                 result = (char *) cur_node->xmlChildrenNode->content;
101                 //sc_debug("%s %s", cur_node->name, result);
102                 return result;
103             }
104             cur_node = cur_node->next;
105         }
106 
107         father = father->xmlChildrenNode;
108         if (father != NULL) cur_node = father->xmlChildrenNode;
109     }
110 
111     return result;
112 }
113 
114 /*
115  * this functions takes the DOM of the styles file
116  * and based on a position, it returns the according numFmtId
117  * IMPORTANT: note that 0 is the first "xf".
118  */
119 /**
120  * \brief TODO Document get_xlsx_styles
121  *
122  * \details This function takes the DOM of the styles file
123  * and mased on position, it returns the according numFmtId.
124  * IMPORTANT: Note that 0 is the first "xf".
125  *
126  * \param[in] doc_styles
127  * \param[in] pos
128  *
129  * \return none
130  */
131 
get_xlsx_styles(xmlDocPtr doc_styles,int pos)132 char * get_xlsx_styles(xmlDocPtr doc_styles, int pos) {
133     // we go forward up to styles data
134     xmlNode * cur_node = xmlDocGetRootElement(doc_styles)->xmlChildrenNode;
135     while (cur_node != NULL && !(cur_node->type == XML_ELEMENT_NODE && !strcmp((char *) cur_node->name, "cellXfs")))
136         cur_node = cur_node->next;
137 
138     cur_node = cur_node->xmlChildrenNode;
139     // we go forward up to desidered numFmtId
140     while (pos--) cur_node = cur_node->next;
141     char * id = (char *) xmlGetProp(cur_node, (xmlChar *) "numFmtId");
142     return id;
143 }
144 
145 /**
146  * \brief TODO Document get_xlsx_number_format_by_id()
147  *
148  * \param[in] doc_styles
149  * \param[in] id
150  *
151  * \return none
152  */
153 
get_xlsx_number_format_by_id(xmlDocPtr doc_styles,int id)154 char * get_xlsx_number_format_by_id(xmlDocPtr doc_styles, int id) {
155     if (doc_styles == NULL || !((id >= 165 && id <= 180) || id == 100))
156         return NULL;
157 
158     // we go forward up to numFmts section
159     xmlNode * cur_node = xmlDocGetRootElement(doc_styles)->xmlChildrenNode;
160     while (cur_node != NULL && !(cur_node->type == XML_ELEMENT_NODE && !strcmp((char *) cur_node->name, "numFmts")))
161         cur_node = cur_node->next;
162 
163     cur_node = cur_node->xmlChildrenNode;
164     // we go forward up to desidered format
165     char * idFile = (char *) xmlGetProp(cur_node, (xmlChar *) "numFmtId");
166     while (atoi(idFile) != id) {
167         cur_node = cur_node->next;
168         free(idFile);
169         idFile = (char *) xmlGetProp(cur_node, (xmlChar *) "numFmtId");
170     }
171 
172     if (atoi(idFile) == id) {
173         free(idFile);
174         return (char *) xmlGetProp(cur_node, (xmlChar *) "formatCode");
175     } else {
176         free(idFile);
177         return NULL;
178     }
179 }
180 
181 /**
182  * \brief TODO Document get_sheet_data()
183  *
184  * \details This function takes the sheetfile DOM and builds the tbl
185  * spreadsheet (SC-IM format)
186  *
187  * \return none
188  */
189 
get_sheet_data(xmlDocPtr doc,xmlDocPtr doc_strings,xmlDocPtr doc_styles)190 void get_sheet_data(xmlDocPtr doc, xmlDocPtr doc_strings, xmlDocPtr doc_styles) {
191     xmlNode * cur_node = xmlDocGetRootElement(doc)->xmlChildrenNode;
192     xmlNode * child_node = NULL;
193     wchar_t line_interp[FBUFLEN] = L"";
194     int r, c;
195 
196     // we go forward up to sheet data
197     while (cur_node != NULL && !(cur_node->type == XML_ELEMENT_NODE && !strcmp((char *) cur_node->name, "sheetData")))
198         cur_node = cur_node->next;
199 
200     cur_node = cur_node->xmlChildrenNode;       // this is sheetdata
201     while (cur_node != NULL) {
202         child_node = cur_node->xmlChildrenNode; // this are rows
203         while (child_node != NULL) {            // this are cols
204 
205             // We get r y c
206             char * row = (char *) xmlGetProp(cur_node, (xmlChar *) "r");
207             r = atoi(row);
208             char * col = (char *) xmlGetProp(child_node, (xmlChar *) "r");
209             while (isdigit(col[strlen(col)-1])) col[strlen(col)-1]='\0';
210             c = atocol(col, strlen(col));
211 
212             char * s = (char *) xmlGetProp(child_node, (xmlChar *) "t"); // type
213             char * style = NULL;
214             style = (char *) xmlGetProp(child_node, (xmlChar *) "s");    // style
215             char * fmtId = style == NULL ? NULL : get_xlsx_styles(doc_styles, atoi(style)); // numfmtId by style number
216             char * numberFmt = NULL;
217             char * shared = NULL;
218             if (fmtId != NULL && atoi(fmtId) != 0) {
219                 numberFmt = get_xlsx_number_format_by_id(doc_styles, atoi(fmtId));
220             }
221 
222             // string
223             if ( s != NULL && ! strcmp(s, "s") ) {
224                 char * st = NULL;
225                 char * strvalue = NULL;
226                 if (child_node->xmlChildrenNode != NULL)
227                     strvalue =  get_xlsx_string(doc_strings, atoi((char *) child_node-> xmlChildrenNode-> xmlChildrenNode->content));
228                 if (strvalue != NULL && strvalue[0] != '\0') {
229                     st = str_replace (strvalue, "\"", "''");
230                     clean_carrier(st); // we handle padding
231                     swprintf(line_interp, FBUFLEN, L"label %s%d=\"%s\"", coltoa(c), r, st);
232                     send_to_interp(line_interp);
233                     free(st);
234                 }
235 
236             // inlinestring
237             } else if ( s != NULL && ! strcmp(s, "inlineStr") ) {
238                 char * st = NULL;
239                 char * strvalue = (char *) child_node->xmlChildrenNode->xmlChildrenNode->xmlChildrenNode->content;
240                 if (strvalue != NULL && strvalue[0] != '\0') {
241                     st = str_replace (strvalue, "\"", "''");
242                     clean_carrier(st); // we handle padding
243                     swprintf(line_interp, FBUFLEN, L"label %s%d=\"%s\"", coltoa(c), r, st);
244                     send_to_interp(line_interp);
245                     free(st);
246                 }
247 
248             // numbers (can be dates, results from formulas or simple numbers)
249             } else {
250                 // date value in v
251                 if (fmtId != NULL && child_node->xmlChildrenNode != NULL &&
252                 ! strcmp((char *) child_node->xmlChildrenNode->name, "v")
253                 && (
254                 (atoi(fmtId) >= 14 && atoi(fmtId) <= 17) ||
255                 atoi(fmtId) == 278 || atoi(fmtId) == 185 ||
256                 atoi(fmtId) == 196 || atoi(fmtId) == 164 ||
257                 atoi(fmtId) == 217 || atoi(fmtId) == 326 ||
258                 (((atoi(fmtId) >= 165 && atoi(fmtId) <= 180) ||
259                 atoi(fmtId) == 100) && numberFmt != NULL // 100,165-180 are user defined formats!!
260                 && str_in_str(numberFmt, "/") != -1)
261                 )) {
262                     long l = strtol((char *) child_node->xmlChildrenNode->xmlChildrenNode->content, (char **) NULL, 10);
263 
264                     swprintf(line_interp, FBUFLEN, L"let %s%d=%.15ld", coltoa(c), r, (l - 25568) * 86400 - get_conf_int("tm_gmtoff"));
265                     send_to_interp(line_interp);
266                     struct ent * n = lookat(r, c);
267                     n->format = 0;
268                     char * stringFormat = scxmalloc((unsigned)(strlen("%d/%m/%Y") + 2));
269                     sprintf(stringFormat, "%c", 'd');
270                     strcat(stringFormat, "%d/%m/%Y");
271                     n->format = stringFormat;
272 
273                 // time value in v
274                 } else if (fmtId != NULL && child_node->xmlChildrenNode != NULL &&
275                 ! strcmp((char *) child_node->xmlChildrenNode->name, "v")
276                 && (
277                 (atoi(fmtId) >= 18 && atoi(fmtId) <= 21)
278                 )) {
279                     double l = atof((char *) child_node->xmlChildrenNode->xmlChildrenNode->content);
280                     swprintf(line_interp, FBUFLEN, L"let %s%d=%.15f", coltoa(c), r, (l - get_conf_int("tm_gmtoff") * 1.0 / 60 / 60 / 24) * 86400);
281                     send_to_interp(line_interp);
282                     struct ent * n = lookat(r, c);
283                     n->format = 0;
284                     char * stringFormat = scxmalloc((unsigned)(strlen("%H:%M:%S") + 2));
285                     sprintf(stringFormat, "%c", 'd');
286                     strcat(stringFormat, "%H:%M:%S");
287                     n->format = stringFormat;
288 
289                 // v - straight int value
290                 } else if (//fmtId != NULL &&
291                 child_node->xmlChildrenNode != NULL &&
292                 ! strcmp((char *) child_node->xmlChildrenNode->name, "v") ){
293                     double l = atof((char *) child_node->xmlChildrenNode->xmlChildrenNode->content);
294                     swprintf(line_interp, FBUFLEN, L"let %s%d=%.15f", coltoa(c), r, l);
295                     send_to_interp(line_interp);
296 
297                 // f - numeric value that is a result from formula
298                 } else if (//fmtId != NULL &&
299                 child_node->xmlChildrenNode != NULL && ! strcmp((char *) child_node->xmlChildrenNode->name, "f")) {
300 
301                     // handle the formula if that is whats desidered!!
302                     if (get_conf_int("xlsx_readformulas") &&
303                         // dont handle shared formulas right now
304                         ! (xmlHasProp(child_node->xmlChildrenNode, (xmlChar *) "t") &&
305                         ! strcmp((shared = (char *) xmlGetProp(child_node->xmlChildrenNode, (xmlChar *) "t")), "shared"))
306                     ) {
307                         char * formula = (char *) child_node->xmlChildrenNode->xmlChildrenNode->content;
308                         char * strf;
309 
310                         // we take some excel common function and adds a @ to them
311                         // we replace count sum avg with @count, @sum, @prod, @avg, @min, @max
312                         strf = str_replace (formula, "COUNT","@COUNT");
313                         strcpy(formula, strf);
314                         free(strf);
315                         strf = str_replace (formula, "SUM","@SUM");
316                         strcpy(formula, strf);
317                         free(strf);
318                         strf = str_replace (formula, "PRODUCT","@PROD");
319                         strcpy(formula, strf);
320                         free(strf);
321                         strf = str_replace (formula, "AVERAGE","@AVG");
322                         strcpy(formula, strf);
323                         free(strf);
324                         strf = str_replace (formula, "MIN","@MIN");
325                         strcpy(formula, strf);
326                         free(strf);
327                         strf = str_replace (formula, "MAX","@MAX");
328                         strcpy(formula, strf);
329                         free(strf);
330                         strf = str_replace (formula, "ABS","@ABS");
331                         strcpy(formula, strf);
332                         free(strf);
333                         strf = str_replace (formula, "STDEV","@STDDEV");
334                         strcpy(formula, strf);
335                         free(strf);
336 
337                         // we send the formula to the interpreter and hope to resolve it!
338                         swprintf(line_interp, FBUFLEN, L"let %s%d=%s", coltoa(c), r, formula);
339 
340                     } else {
341                         double l = atof((char *) child_node->last->xmlChildrenNode->content);
342                         swprintf(line_interp, FBUFLEN, L"let %s%d=%.15f", coltoa(c), r, l);
343                     }
344                     send_to_interp(line_interp);
345                 }
346             }
347 
348             xmlFree(s);
349             xmlFree(fmtId);
350             xmlFree(style);
351             xmlFree(numberFmt);
352             xmlFree(shared);
353 
354             child_node = child_node->next;
355             xmlFree(col);
356             xmlFree(row);
357         }
358         cur_node = cur_node->next;
359     }
360     return;
361 }
362 
363 /**
364  * \brief TODO Document open_xlsx()
365  *
366  * \param[in] fname
367  * \param[in] encoding
368  *
369  * \return none
370  */
371 
open_xlsx(char * fname,char * encoding)372 int open_xlsx(char * fname, char * encoding) {
373     struct zip * za;
374     struct zip_file * zf;
375     struct zip_stat sb, sb_strings, sb_styles, sh_strings;
376     char buf[100];
377     int err;
378     int len;
379 
380     // open zip file
381     if ((za = zip_open(fname, 0, &err)) == NULL) {
382         zip_error_to_str(buf, sizeof(buf), err, errno);
383         sc_error("can't open zip archive `%s': %s", fname, buf);
384         return -1;
385     }
386 
387     // open xl/sharedStrings.xml
388     char * name = "xl/sharedStrings.xml";
389     zf = zip_fopen(za, name, ZIP_FL_UNCHANGED);
390     char * strings = NULL;
391     if (zf) {
392         // some files may not have strings
393         zip_stat(za, name, ZIP_FL_UNCHANGED, &sb_strings);
394         strings = (char *) malloc(sb_strings.size);
395         len = zip_fread(zf, strings, sb_strings.size);
396         if (len < 0) {
397             sc_error("cannot read file %s.\n", name);
398             free(strings);
399             return -1;
400         }
401         zip_fclose(zf);
402     }
403 
404     // open xl/styles.xml
405     name = "xl/styles.xml";
406     zf = zip_fopen(za, name, ZIP_FL_UNCHANGED);
407     if ( ! zf ) {
408         sc_error("cannot open %s file.", name);
409         if (strings != NULL) free(strings);
410         return -1;
411     }
412     zip_stat(za, name, ZIP_FL_UNCHANGED, &sb_styles);
413     char * styles = (char *) malloc(sb_styles.size);
414     len = zip_fread(zf, styles, sb_styles.size);
415     if (len < 0) {
416         sc_error("cannot read file %s.", name);
417         if (strings != NULL) free(strings);
418         free(styles);
419         return -1;
420     }
421     zip_fclose(zf);
422 
423 
424     // find specified sheet
425     if (get_conf_value("sheet") != NULL){
426 
427         //open xml file with sheet names
428         name = "xl/workbook.xml";
429         char namebuf[30];
430         int found = 0;
431         zf = zip_fopen(za, name, ZIP_FL_UNCHANGED);
432 
433         if ( zf ) {
434             zip_stat(za, name, ZIP_FL_UNCHANGED, &sh_strings);
435             char * wb_strings = (char *) malloc(sh_strings.size);
436             len = zip_fread(zf, wb_strings, sh_strings.size);
437             if (len < 0) {
438                 sc_error("cannot read file %s.", name);
439                 free(wb_strings);
440                 return -1;
441             }
442             zip_fclose(zf);
443 
444             // search workbook xml for sheet with the right name
445             xmlDoc * sheet_search = xmlReadMemory(wb_strings, sh_strings.size, "noname.xml", NULL, XML_PARSE_NOBLANKS);
446             xmlNode * cur_node = xmlDocGetRootElement(sheet_search)->xmlChildrenNode;
447             while (cur_node != NULL && strcmp((char *) cur_node->name,"sheets"))
448                 cur_node = cur_node->next;
449             cur_node = cur_node->xmlChildrenNode;
450 
451             char * sheet_name = NULL;
452             while (cur_node != NULL && cur_node->next != NULL && sheet_name == NULL) {
453                 sheet_name = (char *) xmlGetProp(cur_node, (xmlChar *) "name");
454                 if (strcmp(sheet_name, get_conf_value("sheet"))) {
455                     xmlFree(sheet_name);
456                     sheet_name = NULL;
457                     cur_node = cur_node->next;
458                 }
459             }
460             if (sheet_name != NULL){
461                 char * sheet_id = (char *) xmlGetProp(cur_node, (xmlChar *) "sheetId");
462                 snprintf(namebuf,30,"xl/worksheets/sheet%s.xml", sheet_id);
463                 name = namebuf;
464                 found = 1;
465                 xmlFree(sheet_id);
466                 xmlFree(sheet_name);
467             }
468             xmlFreeDoc(sheet_search);
469             if (wb_strings != NULL) free(wb_strings);
470         }
471 
472         if ( ! found ){
473             // use sheet number if sheet name does not match
474             name  = get_conf_value("sheet");
475             int i = strlen(name);
476             while( --i >= 0 && isdigit(name[i]) > 0 );
477             name = i < 0 ? "sheet":"";
478             snprintf(namebuf,30,"xl/worksheets/%s%s.xml",name,get_conf_value("sheet"));
479             name = namebuf;
480         }
481     } else {
482         // select sheet1 if none specified
483         name = "xl/worksheets/sheet1.xml";
484     }
485 
486     //open sheet
487     zf = zip_fopen(za, name, ZIP_FL_UNCHANGED);
488     if ( ! zf ) {
489         sc_error("cannot open %s file.", name);
490         if (strings != NULL) free(strings);
491         free(styles);
492         return -1;
493     }
494     zip_stat(za, name, ZIP_FL_UNCHANGED, &sb);
495     char * sheet = (char *) malloc(sb.size);
496     len = zip_fread(zf, sheet, sb.size);
497     if (len < 0) {
498         sc_error("cannot read file %s.", name);
499         if (strings != NULL) free(strings);
500         free(styles);
501         free(sheet);
502         return -1;
503     }
504     zip_fclose(zf);
505 
506 
507     // XML parse for the sheet file
508     xmlDoc * doc = NULL;
509     xmlDoc * doc_strings = NULL;
510     xmlDoc * doc_styles = NULL;
511 
512     // this initialize the library and check potential ABI mismatches
513     // between the version it was compiled for and the actual shared
514     // library used.
515     LIBXML_TEST_VERSION
516 
517     // parse the file and get the DOM
518     doc_strings = xmlReadMemory(strings, sb_strings.size, "noname.xml", NULL, XML_PARSE_NOBLANKS);
519     doc_styles = xmlReadMemory(styles, sb_styles.size, "noname.xml", NULL, XML_PARSE_NOBLANKS);
520     doc = xmlReadMemory(sheet, sb.size, "noname.xml", NULL, XML_PARSE_NOBLANKS);
521 
522     if (doc == NULL) {
523         sc_error("error: could not parse file");
524         if (strings != NULL) free(strings);
525         free(styles);
526         free(sheet);
527         return -1;
528     }
529 
530     get_sheet_data(doc, doc_strings, doc_styles);
531 
532     // free the document
533     xmlFreeDoc(doc);
534     xmlFreeDoc(doc_strings);
535     xmlFreeDoc(doc_styles);
536 
537     // Free the global variables that may have been allocated by the parser
538     xmlCleanupParser();
539 
540     // free both sheet and strings variables
541     if (strings != NULL) free(strings);
542     free(styles);
543     free(sheet);
544 
545     // close zip file
546     if (zip_close(za) == -1) {
547         sc_error("cannot close zip archive `%s'", fname);
548         return -1;
549     }
550 
551     auto_justify(0, maxcols, DEFWIDTH);
552     deleterow(currow, 1);
553     return 0;
554 }
555 #endif
556 
557 #ifdef XLSX_EXPORT
558 #include "xlsxwriter.h"
559 /**
560  * \brief TODO Document export_xlsx()
561  *
562  * \param[in] filename
563  * \param[in] r0
564  * \param[in] c0
565  * \param[in] rn
566  * \param[in] cn
567  *
568  * \return none
569  */
570 
export_xlsx(char * filename,int r0,int c0,int rn,int cn)571 int export_xlsx(char * filename, int r0, int c0, int rn, int cn) {
572     int row, col;
573     register struct ent ** pp;
574 
575     lxw_workbook  * workbook  = workbook_new(filename);
576     lxw_worksheet * worksheet = workbook_add_worksheet(workbook, NULL);
577 
578     int bkp_currow = currow;
579     currow = 0;
580     insert_row(0); //add a row so that scim formulas apply to excel
581 
582     for (row = r0; row <= rn+1; row++)
583         for (pp = ATBL(tbl, row, col = c0); col <= cn; col++, pp++)
584             if (*pp) {
585                 // Check format here
586                 lxw_format * format = workbook_add_format(workbook);
587 
588                 // handle alignment
589                 if ((*pp)->label && (*pp)->flags & is_label)          // center align
590                     format_set_align(format, LXW_ALIGN_CENTER);
591                 else if ((*pp)->label && (*pp)->flags & is_leftflush) // left align
592                     format_set_align(format, LXW_ALIGN_LEFT);
593                 else if ((*pp)->label)                                // right align
594                     format_set_align(format, LXW_ALIGN_RIGHT);
595 
596                 // handle bold, italic and underline
597                 if ((*pp)->ucolor != NULL && (*pp)->ucolor->bold)
598                     format_set_bold(format);
599                 else if ((*pp)->ucolor != NULL && (*pp)->ucolor->italic)
600                     format_set_italic(format);
601                 else if ((*pp)->ucolor != NULL && (*pp)->ucolor->underline)
602                     format_set_underline(format, LXW_UNDERLINE_SINGLE);
603 
604                 // handle fg color
605                 if ((*pp)->ucolor != NULL && (*pp)->ucolor->fg) {
606                     int fgcolor;
607                     switch ((*pp)->ucolor->fg) {
608                         case BLACK:
609                             fgcolor = LXW_COLOR_BLACK;
610                             break;
611                         case RED:
612                             fgcolor = LXW_COLOR_RED;
613                             break;
614                         case GREEN:
615                             fgcolor = LXW_COLOR_GREEN;
616                             break;
617                         case YELLOW:
618                             fgcolor = LXW_COLOR_YELLOW;
619                             break;
620                         case BLUE:
621                             fgcolor = LXW_COLOR_BLUE;
622                             break;
623                         case MAGENTA:
624                             fgcolor = LXW_COLOR_MAGENTA;
625                             break;
626                         case CYAN:
627                             fgcolor = LXW_COLOR_CYAN;
628                             break;
629                         case WHITE:
630                             fgcolor = LXW_COLOR_WHITE;
631                             break;
632                     }
633                     format_set_font_color(format, fgcolor);
634                 }
635 
636                 // handle bg color
637                 if ((*pp)->ucolor != NULL && (*pp)->ucolor->bg) {
638                     int bgcolor;
639                     switch ((*pp)->ucolor->bg) {
640                         case BLACK:
641                             bgcolor = LXW_COLOR_BLACK;
642                             break;
643                         case RED:
644                             bgcolor = LXW_COLOR_RED;
645                             break;
646                         case GREEN:
647                             bgcolor = LXW_COLOR_GREEN;
648                             break;
649                         case YELLOW:
650                             bgcolor = LXW_COLOR_YELLOW;
651                             break;
652                         case BLUE:
653                             bgcolor = LXW_COLOR_BLUE;
654                             break;
655                         case MAGENTA:
656                             bgcolor = LXW_COLOR_MAGENTA;
657                             break;
658                         case CYAN:
659                             bgcolor = LXW_COLOR_CYAN;
660                             break;
661                         case WHITE:
662                             bgcolor = LXW_COLOR_WHITE;
663                             break;
664                     }
665                     format_set_bg_color(format, bgcolor);
666                 }
667 
668                 // dateformat
669                 if ((*pp) && (*pp)->format && (*pp)->format[0] == 'd') {
670                     char sc_format[BUFFERSIZE];
671                     char * st = NULL;
672                     strcpy(sc_format, &((*pp)->format[1]));
673 
674                     st = str_replace(sc_format, "%Y", "yyyy");
675                     strcpy(sc_format, st);
676                     free(st);
677                     st = str_replace(sc_format, "%y", "yy");
678                     strcpy(sc_format, st);
679                     free(st);
680                     st = str_replace(sc_format, "%m", "mm");
681                     strcpy(sc_format, st);
682                     free(st);
683                     st = str_replace(sc_format, "%d", "dd");
684                     strcpy(sc_format, st);
685                     free(st);
686                     format_set_num_format(format, sc_format);
687                     worksheet_write_number(worksheet, row-1, col, (((*pp)->v + get_conf_int("tm_gmtoff")) / 86400 + 25568) , format);
688 
689                 // formula
690                 } else if ((*pp) && (*pp)->expr && get_conf_int("xlsx_readformulas"))  {
691                     linelim = 0;
692                     editexp((*pp)->row, (*pp)->col);
693                     linelim = -1;
694 
695                     char * strf;
696                     char formula[BUFFERSIZE];
697                     strcpy(formula, line);
698 
699                     strf = str_replace(formula, "@count","count");
700                     strcpy(formula, strf);
701                     free(strf);
702 
703                     strf = str_replace(formula, "@sum","sum");
704                     strcpy(formula, strf);
705                     free(strf);
706 
707                     strf = str_replace(formula, "@prod","product");
708                     strcpy(formula, strf);
709                     free(strf);
710 
711                     strf = str_replace(formula, "@avg","average");
712                     strcpy(formula, strf);
713                     free(strf);
714 
715                     strf = str_replace(formula, "@min","min");
716                     strcpy(formula, strf);
717                     free(strf);
718 
719                     strf = str_replace(formula, "@max","max");
720                     strcpy(formula, strf);
721                     free(strf);
722 
723                     strf = str_replace(formula, "@abs","abs");
724                     strcpy(formula, strf);
725                     free(strf);
726 
727                     strf = str_replace(formula, "@stddev","stdev");
728                     strcpy(formula, strf);
729                     free(strf);
730 
731                     add_char(formula, '=', 0);
732                     worksheet_write_formula(worksheet, row-1, col, formula, NULL);
733 
734                 // If a numeric value exists
735                 } else if ( (*pp)->flags & is_valid) {
736                     worksheet_write_number(worksheet, row-1, col, (*pp)->v, format);
737 
738                 } else if ((*pp)->label) {
739                     worksheet_write_string(worksheet, row-1, col, (*pp)->label, format);
740                 }
741                 /* TODO: handle hidden rows and columns? */
742             }
743     int_deleterow(currow, 1); /* delete the added row */
744     currow = bkp_currow;
745 
746     return workbook_close(workbook);
747 }
748 #endif
749