csvRead: fix C90 compatible variable declaration
[scilab.git] / scilab / modules / spreadsheet / src / c / csvRead.c
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010-2011 - DIGITEO - Allan CORNET
4  * Copyright (C) 2019-2019 - ESI Group - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16 #include "csvRead.h"
17 #include "FileExist.h"
18 #include "Sciwarning.h"
19 #include "configvariable_interface.h"
20 #include "csvDefault.h"
21 #include "expandPathVariable.h"
22 #include "freeArrayOfString.h"
23 #include "localization.h"
24 #include "mclose.h"
25 #include "mgetl.h"
26 #include "mopen.h"
27 #include "os_string.h"
28 #include "pcre_private.h"
29 #include "sci_malloc.h"
30 #include "sciprint.h"
31 #include "splitLine.h"
32 #include "stringToComplex.h"
33 #include "strsubst.h"
34 #include <stdio.h>
35 #include <string.h>
36 // =============================================================================
37 #if _MSC_VER
38 #define READ_ONLY_TEXT_MODE "rt"
39 #else
40 #define READ_ONLY_TEXT_MODE "r"
41 #endif
42 // =============================================================================
43 wchar_t* EMPTY_STR = L"";
44 // =============================================================================
45 static char* getNumbersOfColumnsInLines(wchar_t** lines, int sizelines,
46                                         const wchar_t* separator, int* cols);
47 static int getNumbersOfColumnsInLine(wchar_t* line, const wchar_t* separator);
48 static void removeEmptyLinesAtEnd(wchar_t** lines, int* nonEmptyLines);
49 static void moveEmptyLinesToEnd(wchar_t** lines, int* nonEmptyLines);
50 static void replaceDoubleQuotes(wchar_t** lines, int* nbLines);
51 static int hasOnlyBlankCharacters(wchar_t** line);
52 static void replaceStrings(wchar_t** lines, int* nbLines, wchar_t** toreplace, int sizetoreplace);
53 static wchar_t** extractComments(wchar_t** lines, int* nbLines, const wchar_t* regexpcomments, int* nbcomments, int* iErr);
54 // =============================================================================
55 csvResult* csvRead(const wchar_t* filename, const wchar_t* separator, const wchar_t* decimal, wchar_t** toreplace, int sizetoreplace, const wchar_t* regexpcomments, int header)
56 {
57     wchar_t* expandedFilename = NULL;
58     csvResult* result = NULL;
59     int fd = 0;
60     int f_swap = 0;
61     double res = 0.0;
62     int errMOPEN = MOPEN_INVALID_STATUS;
63     wchar_t** pwstLines = NULL;
64     int nbLines = 0;
65     wchar_t** replacedInLines = NULL;
66     wchar_t** pComments = NULL;
67     int nbComments = 0;
68     wchar_t** pHeader = NULL;
69     int nbHeader = 0;
70
71     if ((filename == NULL) || (separator == NULL) || (decimal == NULL))
72     {
73         return NULL;
74     }
75
76     expandedFilename = expandPathVariableW(filename);
77     if (!FileExistW(expandedFilename))
78     {
79         result = (csvResult*)(CALLOC(1, sizeof(csvResult)));
80         if (result)
81         {
82             result->err = CSV_READ_FILE_NOT_EXIST;
83         }
84
85         FREE(expandedFilename);
86         return result;
87     }
88
89     errMOPEN = mopen(expandedFilename, L"rt", f_swap, &fd); // rt = read only
90     if (expandedFilename)
91     {
92         FREE(expandedFilename);
93         expandedFilename = NULL;
94     }
95
96     if (errMOPEN != MOPEN_NO_ERROR)
97     {
98         result = (csvResult*)(CALLOC(1, sizeof(csvResult)));
99         if (result)
100         {
101             result->err = CSV_READ_MOPEN_ERROR;
102         }
103         return result;
104     }
105
106     if (header != 0)
107     {
108         nbHeader = mgetl(fd, header, &pHeader);
109     }
110
111     nbLines = mgetl(fd, -1, &pwstLines);
112     mclose(fd);
113
114     if (nbLines < 0)
115     {
116         result = (csvResult*)(CALLOC(1, sizeof(csvResult)));
117         if (result)
118         {
119             result->err = CSV_READ_READLINES_ERROR;
120         }
121         freeArrayOfWideString(pHeader, nbHeader);
122         return result;
123     }
124
125     if (regexpcomments)
126     {
127         int iErr = 0;
128
129         pComments = extractComments(pwstLines, &nbLines, regexpcomments, &nbComments, &iErr);
130
131         if ((iErr == CAN_NOT_COMPILE_PATTERN) || (iErr == DELIMITER_NOT_ALPHANUMERIC))
132         {
133             result = (csvResult*)(CALLOC(1, sizeof(csvResult)));
134             if (result)
135             {
136                 if ((iErr == CAN_NOT_COMPILE_PATTERN) || (iErr == DELIMITER_NOT_ALPHANUMERIC))
137                 {
138                     iErr = CSV_READ_REGEXP_ERROR;
139                 }
140                 result->err = (csvReadError)iErr;
141             }
142             freeArrayOfWideString(pHeader, nbHeader);
143             freeArrayOfWideString(pwstLines, nbLines);
144             return result;
145         }
146     }
147
148     if (toreplace && (sizetoreplace > 0))
149     {
150         replaceStrings(pwstLines, &nbLines, toreplace, sizetoreplace);
151     }
152
153     result = (csvResult*)(CALLOC(1, sizeof(csvResult)));
154     if (result)
155     {
156         result->err = CSV_READ_NO_ERROR;
157         result->nbLines = nbLines;
158         result->pwstrValues = pwstLines;
159         result->nbComments = nbComments;
160         result->pwstrComments = pComments;
161         result->nbHeader = nbHeader;
162         result->pwstrHeader = pHeader;
163     }
164     else
165     {
166         freeArrayOfWideString(pComments, nbComments);
167         freeArrayOfWideString(pHeader, nbHeader);
168         freeArrayOfWideString(pwstLines, nbLines);
169     }
170     return result;
171 }
172 // =============================================================================
173 /*
174  * Decode each cell into the previously allocated memory, this code should not allocate per-cell
175  * but rather manipulate the input lines to speed the decoding.
176  *
177  * Note: some global allocation are also performed depending in some parsing condition. For example,
178  * the image buffer of complex numbers is allocated only when a complex number is detected.
179  */
180
181 int csvTextScanInPlace(wchar_t** text, int nbLines, const wchar_t* separator,
182                        const wchar_t* decimal, int haveRange, const int* iRange, int m1, int n1,
183                        wchar_t** pstrValues, double* pDblRealValues, double** pDblImgValues)
184 {
185     stringToComplexError ierr = STRINGTOCOMPLEX_ERROR;
186
187     // per line
188     for (int i = 0; i < nbLines; i++)
189     {
190         wchar_t* start = NULL;
191         wchar_t* end = NULL;
192         wchar_t* previousEnd = NULL;
193         wchar_t* iter = text[i];
194
195         // skip some values to be within the range
196         if (haveRange)
197         {
198             if (i < iRange[0] - 1)
199             {
200                 continue;
201             }
202             if (i >= iRange[2])
203             {
204                 break;
205             }
206         }
207
208         // per cell (column)
209         for (int j = 0; iter != NULL; j++)
210         {
211             int zeroIndex = i + nbLines * j;
212
213             iter = splitLineCSVNext(iter, separator, &start, &end);
214
215             // skip some values to be within the range
216             if (haveRange)
217             {
218                 if (j < iRange[1] - 1)
219                 {
220                     continue;
221                 }
222                 if (j >= iRange[3])
223                 {
224                     break;
225                 }
226                 // adapt zeroIndex to be within the range
227                 zeroIndex = i - iRange[0] + 1 + (iRange[2] - iRange[0] + 1) * (j - iRange[1] + 1);
228             }
229
230             // decode in double or string
231             if (pDblRealValues != NULL)
232             {
233                 doublecomplex v = stringToComplexWInPlace(start, end, decimal,
234                                                           TRUE, &ierr);
235                 if (ierr == STRINGTOCOMPLEX_NO_ERROR)
236                 {
237                     // the imag part of a complex number is allocated on demand
238                     if (v.i != 0. && *pDblImgValues == NULL)
239                     {
240                         size_t n;
241                         if (haveRange)
242                         {
243                             n = (iRange[2] - iRange[0] + 1) * (iRange[3] - iRange[1] + 1) * sizeof(double);
244                         }
245                         else
246                         {
247                             n = m1 * n1 * sizeof(double);
248                         }
249                         *pDblImgValues = CALLOC(1, n);
250                         if (*pDblImgValues == NULL)
251                         {
252                             return -1;
253                         }
254                     }
255                     if (v.i != 0.)
256                     {
257                         (*pDblImgValues)[zeroIndex] = v.i;
258                     }
259                     pDblRealValues[zeroIndex] = v.r;
260                 }
261             }
262             else
263             {
264                 // finish a previously stored "end" by a trailing '\0', needed for further iteration *BUT* easy strdup() at the end
265                 if (previousEnd)
266                 {
267                     *previousEnd = '\0';
268                 }
269                 previousEnd = end;
270
271                 // convert the passed decimal to a dot
272                 convertDecimalToDotInPlace(start, decimal);
273
274                 // escape double quotes
275                 escapeDoubleQuotesInPlace(start, end);
276
277                 pstrValues[zeroIndex] = start;
278             }
279         }
280     }
281     return 0;
282 }
283 // =============================================================================
284 static void replaceDoubleQuotes(wchar_t** lines, int* nbLines)
285 {
286     int dest = 0;
287     wchar_t* it = NULL;
288     for (int next = dest; next < *nbLines; dest++, next++)
289     {
290         int quoteCount = 0;
291         lines[dest] = lines[next];
292
293         it = lines[dest];
294         for (; *it != L'\0'; it++)
295         {
296             if (*it == L'"')
297             {
298                 quoteCount++;
299             }
300         }
301
302         // if at the end of this line, we still have an open double quote.
303         // merge this line and the followings.
304         for (; quoteCount % 2 == 1 && next + 1 < *nbLines; next++)
305         {
306             wchar_t* currentLine = lines[dest];
307             wchar_t* nextLine = lines[next + 1];
308
309             size_t currentLen = it - lines[dest];
310             size_t nextLen = wcslen(nextLine);
311             size_t newLen = currentLen + nextLen + 2; // includes 'LF' and '\0'
312
313             lines[dest] = MALLOC(newLen * sizeof(wchar_t));
314             memcpy(lines[dest], currentLine, currentLen * sizeof(wchar_t));
315             *(lines[dest] + currentLen) = 0x0A; // LF
316             memcpy(lines[dest] + currentLen + 1, nextLine, nextLen * sizeof(wchar_t));
317             *(lines[dest] + currentLen + 1 + nextLen) = L'\0';
318
319             FREE(currentLine);
320             FREE(nextLine);
321
322             it = lines[dest] + currentLen;
323             for (; *it != L'\0'; it++)
324             {
325                 if (*it == L'"')
326                 {
327                     quoteCount++;
328                 }
329             }
330         }
331     }
332
333     *nbLines = dest;
334 }
335 // =============================================================================
336 char* csvTextScanSize(wchar_t** lines, int* nbLines, const wchar_t* separator, int* rows, int* cols, int haveRange, int* iRange)
337 {
338     char* errorMsg = NULL;
339
340     // ticket 472
341     {
342         const char* blankMode = getCsvDefaultCsvIgnoreBlankLine();
343         if (strcmp(blankMode, "on") == 0)
344         {
345             moveEmptyLinesToEnd(lines, nbLines);
346         }
347         else
348         {
349             /* remove last lines empty (bug 7003 in scilab)*/
350             removeEmptyLinesAtEnd(lines, nbLines);
351         }
352     }
353
354     // escape line breaks (CRLF) inside double quotes
355     // Note: this reallocate and change the number of lines to restore valid
356     //       lines and number of lines.
357     replaceDoubleQuotes(lines, nbLines);
358
359     errorMsg = getNumbersOfColumnsInLines(lines, *nbLines, separator, cols);
360     *rows = *nbLines;
361     if (errorMsg != NULL)
362     {
363         return errorMsg;
364     }
365
366     // update the expected range if needed
367     if (haveRange)
368     {
369         // error
370         if (iRange[0] > *rows)
371         {
372             return gettext("%s: Range row or/and column left indice(s) out of bounds.\n");
373         }
374         if (iRange[1] > *cols)
375         {
376             return gettext("%s: Range row or/and column left indice(s) out of bounds.\n");
377         }
378
379         // truncate
380         if (iRange[2] > *rows)
381         {
382             iRange[2] = *rows;
383         }
384         if (iRange[3] > *cols)
385         {
386             iRange[3] = *cols;
387         }
388     }
389
390     return NULL;
391 }
392 // =============================================================================
393 void freeCsvResult(csvResult* result)
394 {
395     if (result)
396     {
397         if (result->pwstrValues)
398         {
399             freeArrayOfWideString(result->pwstrValues, result->nbLines);
400             result->pwstrValues = NULL;
401         }
402         result->nbLines = 0;
403
404         if (result->pwstrComments)
405         {
406             freeArrayOfWideString(result->pwstrComments, result->nbComments);
407             result->pwstrComments = NULL;
408         }
409         if (result->pwstrHeader)
410         {
411             freeArrayOfWideString(result->pwstrHeader, result->nbHeader);
412             result->pwstrHeader = NULL;
413         }
414         result->err = CSV_READ_ERROR;
415         FREE(result);
416         result = NULL;
417     }
418 }
419 // =============================================================================
420 static char* getNumbersOfColumnsInLines(wchar_t** lines, int sizelines,
421                                         const wchar_t* separator, int* cols)
422 {
423     int previousNbColumns = 0;
424     int NbColumns = 0;
425     BOOL firstLine = TRUE;
426     if (lines)
427     {
428         int i = 0;
429         for (i = 0; i < sizelines; i++)
430         {
431             NbColumns = getNumbersOfColumnsInLine(lines[i], separator);
432             if (firstLine)
433             {
434                 previousNbColumns = NbColumns;
435                 firstLine = FALSE;
436             }
437             else
438             {
439                 if (previousNbColumns != NbColumns)
440                 {
441                     Sciwarning(_("%s: Inconsistency found in the columns. At line %d, found %d columns while the previous had %d.\n"), _("Warning"), i + 1, NbColumns, previousNbColumns);
442                     return gettext("%s: can not read file, error in the column structure\n");
443                 }
444             }
445         }
446     }
447     *cols = NbColumns;
448     return NULL;
449 }
450 // =============================================================================
451 static int getNumbersOfColumnsInLine(wchar_t* line, const wchar_t* separator)
452 {
453     int nbTokens = 0;
454
455     if (line && separator)
456     {
457         wchar_t* start = NULL;
458         wchar_t* end = NULL;
459
460         wchar_t* iter = line;
461         while (iter != NULL)
462         {
463             nbTokens++;
464
465             iter = splitLineCSVNext(iter, separator, &start, &end);
466         }
467     }
468     return nbTokens;
469 }
470 // =============================================================================
471 static void removeEmptyLinesAtEnd(wchar_t** lines, int* nonEmptyLines)
472 {
473     const int len = *nonEmptyLines;
474
475     if (lines)
476     {
477         int i = len - 1;
478         for (; i >= 0; i--)
479         {
480             if (!hasOnlyBlankCharacters(&lines[i]))
481             {
482                 break;
483             }
484         }
485
486         *nonEmptyLines = i + 1;
487     }
488 }
489 // =============================================================================
490 static void moveEmptyLinesToEnd(wchar_t** lines, int* nonEmptyLines)
491 {
492     if (lines)
493     {
494         // move the blank lines at the end
495         int last = *nonEmptyLines - 1;
496         for (int i = last; i >= 0; i--)
497         {
498             if (hasOnlyBlankCharacters(&lines[i]))
499             {
500                 // swap
501                 wchar_t* str = lines[i];
502                 memmove(&lines[i], &lines[i+1], (last - i + 1) * sizeof(wchar_t*));
503                 lines[last] = str;
504                 last--;
505             }
506         }
507
508         *nonEmptyLines = last + 1;
509     }
510 }
511 // =============================================================================
512 static int hasOnlyBlankCharacters(wchar_t** line)
513 {
514     const wchar_t* iter = *line;
515
516     // EMPTY_STR is a tagged, non-allocated value for the empty string. It is 
517     // used to flag a blank string.
518     if (*line == EMPTY_STR)
519     {
520         return 1;
521     }
522
523     while (*iter == L'\t' || *iter == L'\r' || *iter == L'\n' || *iter == L' ')
524     {
525         iter++;
526     }
527
528     if (*iter == L'\0')
529     {
530         // flag the empty line as really empty, further comparisons will be cheap
531         FREE(*line);
532         *line = EMPTY_STR;
533         return 1;
534     }
535     else
536     {
537         return 0;
538     }
539 }
540 // =============================================================================
541 static void replaceStrings(wchar_t** lines, int* nbLines, wchar_t** toreplace, int sizetoreplace)
542 {
543     // number of string to compare and replace
544     int nr = sizetoreplace / 2;
545
546     if (nbLines == NULL || *nbLines == 0 || sizetoreplace == 0)
547     {
548         return;
549     }
550     if (lines == NULL || toreplace == NULL)
551     {
552         return;
553     }
554
555     // for each line
556     for (int i = 0; i < *nbLines; i++)
557     {
558         // for each string to compare with
559         for (int j = 0; j < nr; j++)
560         {
561             wchar_t* found = wcsstr(lines[i], toreplace[j]);
562             if (found)
563             {
564                 // we found a matching string, either reallocate and copy OR
565                 // copy in place if there is enough room for the replacement
566                 size_t lineLen = wcslen(lines[i]);
567                 size_t foundLen = wcslen(found);
568                 size_t matchLen = wcslen(toreplace[j]);
569                 size_t replaceLen = wcslen(toreplace[j + nr]);
570                 if (replaceLen > matchLen)
571                 {
572                     wchar_t* previousLine = lines[i];
573                     size_t allocatedLen = lineLen - matchLen + replaceLen + 1;
574                     size_t foundIndex = found - previousLine;
575
576                     lines[i] = MALLOC(allocatedLen * sizeof(wchar_t));
577                     // copy up to the found string
578                     memcpy(lines[i], previousLine, foundIndex * sizeof(wchar_t));
579                     // copy the replacement string
580                     memcpy(&lines[i][foundIndex], toreplace[j + nr], replaceLen * sizeof(wchar_t));
581                     // copy from the end of the match up to the end of line
582                     memcpy(&lines[i][foundIndex + replaceLen], &previousLine[foundIndex + matchLen], (allocatedLen - foundIndex - replaceLen) * sizeof(wchar_t));
583
584                     FREE(previousLine);
585                 }
586                 else
587                 {
588                     // copy the replacement string
589                     memcpy(found, toreplace[j + nr], replaceLen * sizeof(wchar_t));
590                     // move the remaining string next to the replaced string (including the trailing 0)
591                     memmove(&found[replaceLen], &found[matchLen], (foundLen - matchLen + 1) * sizeof(wchar_t));
592                 }
593
594                 // continue to replace
595                 j--;
596             }
597         }
598     }
599 }
600 // =============================================================================
601 static wchar_t** extractComments(wchar_t** lines, int* nbLines,
602                                  const wchar_t* regexpcomments, int* nbcomments, int* iErr)
603 {
604     wchar_t** pComments = NULL;
605     int i = 0;
606
607     for (i = 0; i < *nbLines; i++)
608     {
609         int Output_Start = 0;
610         int Output_End = 0;
611         pcre_error_code answer = wide_pcre_private(lines[i], regexpcomments, &Output_Start, &Output_End, NULL, NULL);
612
613         if ((answer == CAN_NOT_COMPILE_PATTERN) || (answer == DELIMITER_NOT_ALPHANUMERIC))
614         {
615             if (pComments)
616             {
617                 freeArrayOfWideString(pComments, *nbcomments);
618             }
619
620             *nbcomments = 0;
621
622             *iErr = answer;
623             return NULL;
624         }
625
626         if (answer == PCRE_FINISHED_OK)
627         {
628             (*nbcomments)++;
629             if (pComments == NULL)
630             {
631                 pComments = (wchar_t**)MALLOC(sizeof(wchar_t*) * (*nbcomments));
632             }
633             else
634             {
635                 pComments = (wchar_t**)REALLOC(pComments, sizeof(wchar_t*) * (*nbcomments));
636             }
637
638             if (pComments == NULL)
639             {
640                 *nbcomments = 0;
641                 *iErr = 1;
642                 return NULL;
643             }
644
645             // move lines[i] to comments and continue
646             pComments[(*nbcomments) - 1] = lines[i];
647             memmove(lines + i, lines + i + 1, sizeof(wchar_t*) * (*nbLines - i - 1));
648             (*nbLines)--;
649             i--;
650         }
651     }
652
653     return pComments;
654 }
655 // =============================================================================