fileio: mgetl rewritten for better performance
[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  *
5  * Copyright (C) 2012 - 2016 - Scilab Enterprises
6  *
7  * This file is hereby licensed under the terms of the GNU GPL v2.0,
8  * pursuant to article 5.3.4 of the CeCILL v.2.1.
9  * This file was originally licensed under the terms of the CeCILL v2.1,
10  * and continues to be available under such terms.
11  * For more information, see the COPYING file which you should have received
12  * along with this program.
13  *
14  */
15 #include <string.h>
16 #include <stdio.h>
17 #include "csvRead.h"
18 #include "sci_malloc.h"
19 #include "freeArrayOfString.h"
20 #include "mopen.h"
21 #include "mgetl.h"
22 #include "localization.h"
23 #include "expandPathVariable.h"
24 #include "FileExist.h"
25 #include "mclose.h"
26 #include "configvariable_interface.h"
27 #include "pcre_private.h"
28 #include "sciprint.h"
29 #include "splitLine.h"
30 #include "os_string.h"
31 #include "csvDefault.h"
32 #include "strsubst.h"
33 #include "Sciwarning.h"
34 // =============================================================================
35 #if _MSC_VER
36 #define READ_ONLY_TEXT_MODE "rt"
37 #else
38 #define READ_ONLY_TEXT_MODE "r"
39 #endif
40 // =============================================================================
41 static int getNumbersOfColumnsInLines(const char **lines, int sizelines,
42                                       const char *separator);
43 static int getNumbersOfColumnsInLine(const char *line, const char *separator);
44 static char **getStringsFromLines(const char **lines, int sizelines,
45                                   const char *separator, const char *decimal,
46                                   int m, int n);
47 static char **removeEmptyLinesAtTheEnd(const char **lines, int *sizelines);
48 static char *stripCharacters(const char *line);
49 static char **replaceStrings(const char **lines, int nbLines, const char **toreplace, int sizetoreplace);
50 static char **extractComments(const char **lines, int nbLines, const char *regexpcomments, int *nbcomments, int *iErr);
51 static char **removeComments(const char **lines, int nbLines, const char *regexpcomments, int *nbNewLine, int *iErr);
52 static char **removeAllBlankLines(const char **lines, int *sizelines);
53 // =============================================================================
54 csvResult* csvRead(const char *filename, const char *separator, const char *decimal, const char **toreplace, int sizetoreplace, const char *regexpcomments, int header)
55 {
56     wchar_t *expandedFilename = NULL;
57     wchar_t *wideFilename = 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     char **pstLines = NULL;
65     int nbLines = 0;
66     char **replacedInLines = NULL;
67     char **pComments = NULL;
68     int nbComments = 0;
69
70     if ((filename == NULL) || (separator == NULL) || (decimal == NULL))
71     {
72         return NULL;
73     }
74
75     wideFilename = to_wide_string((char*)filename);
76     expandedFilename = expandPathVariableW(wideFilename);
77     FREE(wideFilename);
78
79     if (!FileExistW(expandedFilename))
80     {
81         result = (csvResult*)(MALLOC(sizeof(csvResult)));
82         if (result)
83         {
84             result->err = CSV_READ_FILE_NOT_EXIST;
85             result->m = 0;
86             result->n = 0;
87             result->pstrValues = NULL;
88             result->pstrComments = NULL;
89             result->nbComments = 0;
90         }
91
92         FREE(expandedFilename);
93         return result;
94     }
95
96     errMOPEN = mopen(expandedFilename, L"rt", f_swap, &fd); // rt = read only
97     if (expandedFilename)
98     {
99         FREE(expandedFilename);
100         expandedFilename = NULL;
101     }
102
103     if (errMOPEN != MOPEN_NO_ERROR)
104     {
105         result = (csvResult*)(MALLOC(sizeof(csvResult)));
106         if (result)
107         {
108             result->err = CSV_READ_MOPEN_ERROR;
109             result->m = 0;
110             result->n = 0;
111             result->pstrValues = NULL;
112             result->pstrComments = NULL;
113             result->nbComments = 0;
114
115         }
116         return result;
117     }
118
119     if (header != 0)
120     {
121         wchar_t **pwstHeaderLines = NULL;
122         mgetl(fd, header, &pwstHeaderLines);
123         FREE(pwstHeaderLines);
124     }
125
126     nbLines = mgetl(fd, -1, &pwstLines);
127     mclose(fd);
128
129     if (nbLines >= 0)
130     {
131         int i = 0;
132         pstLines = (char**)MALLOC(sizeof(char*) * nbLines);
133         for (i = 0 ; i < nbLines ; i++)
134         {
135             pstLines[i] = wide_string_to_UTF8(pwstLines[i]);
136         }
137         freeArrayOfWideString(pwstLines, nbLines);
138         pwstLines = NULL;
139     }
140     else
141     {
142         result = (csvResult*)(MALLOC(sizeof(csvResult)));
143         if (result)
144         {
145             result->err = CSV_READ_READLINES_ERROR;
146             result->m = 0;
147             result->n = 0;
148             result->pstrValues = NULL;
149             result->pstrComments = NULL;
150             result->nbComments = 0;
151         }
152         return result;
153     }
154
155     if (regexpcomments)
156     {
157         int iErr = 0;
158
159         pComments = extractComments((const char**)pstLines, nbLines, regexpcomments, &nbComments, &iErr);
160
161         if ((iErr == CAN_NOT_COMPILE_PATTERN) || (iErr == DELIMITER_NOT_ALPHANUMERIC))
162         {
163             result = (csvResult*)(MALLOC(sizeof(csvResult)));
164             if (result)
165             {
166                 if ((iErr == CAN_NOT_COMPILE_PATTERN) || (iErr == DELIMITER_NOT_ALPHANUMERIC))
167                 {
168                     iErr = CSV_READ_REGEXP_ERROR;
169                 }
170                 result->err = (csvReadError)iErr;
171                 result->m = 0;
172                 result->n = 0;
173                 result->pstrValues = NULL;
174                 result->pstrComments = NULL;
175                 result->nbComments = 0;
176             }
177             freeArrayOfString(pstLines, nbLines);
178             return result;
179         }
180
181         if (pComments)
182         {
183             char **pCleanedLines = NULL;
184             int nbCleanedLines = 0;
185             int i = 0;
186
187             pCleanedLines = removeComments((const char**)pstLines, nbLines, (const char*)regexpcomments, &nbCleanedLines, &iErr);
188             if (pCleanedLines)
189             {
190                 if (pwstLines)
191                 {
192                     freeArrayOfWideString(pwstLines, nbLines);
193                     pwstLines = NULL;
194                 }
195                 FREE(pstLines);
196                 pstLines = pCleanedLines;
197                 nbLines = nbCleanedLines;
198             }
199
200         }
201     }
202
203     if (toreplace && (sizetoreplace > 0))
204     {
205         replacedInLines = replaceStrings((const char**)pstLines, nbLines, toreplace, sizetoreplace);
206         if (replacedInLines)
207         {
208             freeArrayOfString(pstLines, nbLines);
209             pstLines = replacedInLines;
210         }
211     }
212
213     result = csvTextScan((const char**)pstLines, nbLines, (const char*)separator, (const char*)decimal);
214     freeArrayOfString(pstLines, nbLines);
215     freeArrayOfWideString(pwstLines, nbLines);
216
217     if (result)
218     {
219         result->pstrComments = pComments;
220         result->nbComments = nbComments;
221     }
222     else
223     {
224         freeArrayOfString(pComments, nbComments);
225     }
226
227     return result;
228 }
229 // =============================================================================
230 csvResult* csvTextScan(const char **lines, int numberOfLines, const char *separator, const char *decimal)
231 {
232     csvResult *result = NULL;
233     int nbRows = 0;
234     int nbColumns = 0;
235     char **cellsStrings = NULL;
236     char **cleanedLines = NULL;
237     int nbLines = numberOfLines;
238
239     if (strcmp(separator, decimal) == 0)
240     {
241         result = (csvResult*)(MALLOC(sizeof(csvResult)));
242         if (result)
243         {
244             result->err = CSV_READ_SEPARATOR_DECIMAL_EQUAL;
245             result->m = 0;
246             result->n = 0;
247             result->pstrValues = NULL;
248             result->pstrComments = NULL;
249             result->nbComments = 0;
250         }
251         return result;
252     }
253
254     // ticket 472
255     {
256         const char *blankMode = getCsvDefaultCsvIgnoreBlankLine();
257         if (strcmp(blankMode, "on") == 0)
258         {
259             char **tmpLines = removeAllBlankLines(lines, &nbLines);
260             if (tmpLines)
261             {
262                 freeArrayOfString(cleanedLines, nbLines);
263                 cleanedLines = tmpLines;
264             }
265         }
266         else
267         {
268             /* remove last lines empty (bug 7003 in scilab)*/
269             cleanedLines = removeEmptyLinesAtTheEnd(lines, &nbLines);
270         }
271     }
272
273     nbColumns = getNumbersOfColumnsInLines((const char **)cleanedLines, nbLines, separator);
274     if (nbColumns == 0)
275     {
276         result = (csvResult*)(MALLOC(sizeof(csvResult)));
277         if (result)
278         {
279             result->err = CSV_READ_COLUMNS_ERROR;
280             result->m = 0;
281             result->n = 0;
282             result->pstrValues = NULL;
283             result->pstrComments = NULL;
284             result->nbComments = 0;
285         }
286         FREE(cleanedLines);
287         return result;
288     }
289     else
290     {
291         nbRows = nbLines;
292     }
293
294     cellsStrings = getStringsFromLines((const char **)cleanedLines, nbLines, separator, decimal, nbColumns, nbRows);
295     if (cleanedLines)
296     {
297         freeArrayOfString(cleanedLines, nbLines);
298         cleanedLines = NULL;
299     }
300
301     if (cellsStrings)
302     {
303         result = (csvResult*)(MALLOC(sizeof(csvResult)));
304         if (result)
305         {
306             result->err = CSV_READ_NO_ERROR;
307             result->m = nbRows;
308             result->n = nbColumns;
309             result->pstrValues = cellsStrings;
310             result->pstrComments = NULL;
311             result->nbComments = 0;
312         }
313         else
314         {
315             FREE(cellsStrings);
316         }
317     }
318     else
319     {
320         result = (csvResult*)(MALLOC(sizeof(csvResult)));
321         if (result)
322         {
323             result->err = CSV_READ_COLUMNS_ERROR;
324             result->m = 0;
325             result->n = 0;
326             result->pstrValues = NULL;
327             result->pstrComments = NULL;
328             result->nbComments = 0;
329         }
330     }
331     return result;
332 }
333 // =============================================================================
334 void freeCsvResult(csvResult *result)
335 {
336     if (result)
337     {
338         if (result->pstrValues)
339         {
340             freeArrayOfString(result->pstrValues, result->m * result->n);
341             result->pstrValues = NULL;
342         }
343         result->m = 0;
344         result->n = 0;
345
346         if (result->pstrComments)
347         {
348             freeArrayOfString(result->pstrComments, result->nbComments);
349             result->pstrComments = NULL;
350         }
351         result->err = CSV_READ_ERROR;
352         FREE(result);
353         result = NULL;
354     }
355 }
356 // =============================================================================
357 static int getNumbersOfColumnsInLines(const char **lines, int sizelines,
358                                       const char *separator)
359 {
360     int previousNbColumns = 0;
361     int NbColumns = 0;
362     BOOL firstLine = TRUE;
363     if (lines)
364     {
365         int i = 0;
366         for (i = 0; i < sizelines; i++)
367         {
368             NbColumns = getNumbersOfColumnsInLine(lines[i], separator);
369             if (firstLine)
370             {
371                 previousNbColumns = NbColumns;
372                 firstLine = FALSE;
373             }
374             else
375             {
376                 if (previousNbColumns != NbColumns)
377                 {
378                     Sciwarning(_("%s: Inconsistency found in the columns. At line %d, found %d columns while the previous had %d.\n"), _("Warning"), i + 1, NbColumns, previousNbColumns);
379                     return 0;
380                 }
381             }
382         }
383     }
384     return NbColumns;
385 }
386 // =============================================================================
387 static int getNumbersOfColumnsInLine(const char *line, const char *separator)
388 {
389     if (line && separator)
390     {
391         int i = 0;
392         int nbTokens = 0;
393         char **splittedStr = splitLineCSV(line, separator, &nbTokens);
394         if (splittedStr)
395         {
396             freeArrayOfString(splittedStr, nbTokens);
397             return nbTokens;
398         }
399         else
400         {
401             int len = (int)strlen(line);
402             if (len > 0)
403             {
404                 nbTokens = 1;
405                 return nbTokens;
406             }
407         }
408     }
409     return 0;
410 }
411 // =============================================================================
412 static char **getStringsFromLines(const char **lines, int sizelines,
413                                   const char *separator,
414                                   const char *decimal,
415                                   int m, int n)
416 {
417     char **results = NULL;
418
419     if (lines == NULL)
420     {
421         return NULL;
422     }
423     if (separator == NULL)
424     {
425         return NULL;
426     }
427     if (m == 0 || n == 0)
428     {
429         return NULL;
430     }
431
432     results = (char**) MALLOC(sizeof(char*) * (m * n));
433     if (results)
434     {
435         int i = 0;
436         for (i = 0; i < sizelines; i++)
437         {
438             int nbTokens = 0;
439             char **lineStrings = splitLineCSV(lines[i], separator, &nbTokens);
440             int j = 0;
441
442             if (lineStrings == NULL)
443             {
444                 lineStrings = (char**)MALLOC(sizeof(char*) * 1);
445                 lineStrings[0] = os_strdup(lines[i]);
446                 nbTokens = 1;
447             }
448
449             if (m != nbTokens)
450             {
451                 freeArrayOfString(results, nbTokens * n);
452                 FREE(lineStrings);
453                 return NULL;
454             }
455
456             for (j = 0; j < m; j++)
457             {
458
459                 if (!decimal)
460                 {
461                     results[i + n * j] = os_strdup(lineStrings[j]);
462                 }
463                 else
464                 {
465                     /* Proceed to the remplacement of the provided decimal to the default on
466                      * usually, it converts "," => "." */
467                     results[i + n * j] = strsub(lineStrings[j], decimal, getCsvDefaultDecimal());
468                 }
469
470                 if (lineStrings[j])
471                 {
472                     FREE(lineStrings[j]);
473                     lineStrings[j] = NULL;
474                 }
475             }
476             FREE(lineStrings);
477         }
478     }
479     return results;
480 }
481 // =============================================================================
482 static char **removeEmptyLinesAtTheEnd(const char **lines, int *sizelines)
483 {
484     char **returnedLines = NULL;
485     int nbLinesToRemove = 0;
486
487     if (lines)
488     {
489         int i = 0;
490         if (*sizelines >= 1)
491         {
492             for (i = *sizelines - 1; i >= 0; i--)
493             {
494                 char *cleanedLine = stripCharacters(lines[i]);
495                 if (cleanedLine)
496                 {
497                     int len = (int) strlen(cleanedLine);
498                     FREE(cleanedLine);
499                     cleanedLine = NULL;
500                     if (len == 0)
501                     {
502                         nbLinesToRemove++;
503                         FREE((char*)lines[i]);
504                         lines[i] = NULL;
505                     }
506                     else
507                     {
508                         break;
509                     }
510                 }
511             }
512
513             if (nbLinesToRemove > 0)
514             {
515                 *sizelines = *sizelines - nbLinesToRemove;
516             }
517             returnedLines = (char **)MALLOC(sizeof(char *) * (*sizelines));
518             if (returnedLines)
519             {
520                 for (i = 0; i < *sizelines; i++)
521                 {
522                     returnedLines[i] = os_strdup(lines[i]);
523                 }
524             }
525         }
526     }
527
528     return returnedLines;
529 }
530 // =============================================================================
531 static char **removeAllBlankLines(const char **lines, int *sizelines)
532 {
533     char **returnedLines = NULL;
534     int nbLines = 0;
535     if (lines)
536     {
537         int i = 0;
538         for (i = 0; i < *sizelines; i++)
539         {
540             char *cleanedLine = stripCharacters(lines[i]);
541             if (cleanedLine)
542             {
543                 int len = (int) strlen(cleanedLine);
544                 FREE(cleanedLine);
545                 cleanedLine = NULL;
546                 if (len != 0)
547                 {
548                     if (nbLines == 0)
549                     {
550                         nbLines++;
551                         returnedLines = (char**)MALLOC(sizeof(char*) * nbLines);
552                     }
553                     else
554                     {
555                         nbLines++;
556                         returnedLines = (char**)REALLOC(returnedLines, sizeof(char*) * nbLines);
557                     }
558
559                     if (returnedLines)
560                     {
561                         returnedLines[nbLines - 1] = os_strdup(lines[i]);
562                     }
563                     else
564                     {
565                         *sizelines = 0;
566                         return NULL;
567                     }
568                 }
569             }
570         }
571         *sizelines = nbLines;
572     }
573     return returnedLines;
574 }
575 // =============================================================================
576 static char *stripCharacters(const char *line)
577 {
578     char *returnedLine = NULL;
579     if (line)
580     {
581         char *tmpLineWithoutTab = strsub((char*)line, "\t", "");
582         if (tmpLineWithoutTab)
583         {
584             char *tmpLineWithoutLF = strsub(tmpLineWithoutTab, "\r", "");
585             if (tmpLineWithoutLF)
586             {
587                 char *tmpLineWithoutCR = strsub(tmpLineWithoutTab, "\n", "");
588                 if (tmpLineWithoutCR)
589                 {
590                     returnedLine = strsub(tmpLineWithoutCR, " ", "");
591                     FREE(tmpLineWithoutCR);
592                 }
593                 else
594                 {
595                     returnedLine = os_strdup(line);
596                 }
597                 FREE(tmpLineWithoutLF);
598                 tmpLineWithoutLF = NULL;
599             }
600             else
601             {
602                 returnedLine = os_strdup(line);
603             }
604             FREE(tmpLineWithoutTab);
605             tmpLineWithoutTab = NULL;
606         }
607         else
608         {
609             returnedLine = os_strdup(line);
610         }
611     }
612
613     return returnedLine;
614 }
615 // =============================================================================
616 static char **replaceStrings(const char **lines, int nbLines, const char **toreplace, int sizetoreplace)
617 {
618     char **replacedStrings = NULL;
619     int nr = 0;
620
621     nr = sizetoreplace / 2;
622
623     if (lines)
624     {
625         int i = 0;
626
627         replacedStrings = (char**)MALLOC(sizeof(char*) * nbLines);
628         if (replacedStrings)
629         {
630             // Copy the source lines to the target replacedStrings.
631             int j = 0;
632             for (j = 0; j < nbLines; j++)
633             {
634                 replacedStrings[j] = os_strdup(lines[j]);
635             }
636             // Make replacements within the target replacedStrings.
637             for (i = 0; i < nr; i++)
638             {
639                 for (j = 0; j < nbLines; j++)
640                 {
641                     replacedStrings[j] = strsub(replacedStrings[j], toreplace[i], toreplace[nr + i]);
642                 }
643             }
644         }
645     }
646     return replacedStrings;
647 }
648 // =============================================================================
649 static char **extractComments(const char **lines, int nbLines,
650                               const char *regexpcomments, int *nbcomments, int *iErr)
651 {
652     char **pComments = NULL;
653     int i = 0;
654
655     for (i = 0; i < nbLines; i++)
656     {
657         int Output_Start = 0;
658         int Output_End = 0;
659         pcre_error_code answer = pcre_private((char*)lines[i], (char*)regexpcomments, &Output_Start, &Output_End, NULL, NULL);
660
661         if ( (answer == CAN_NOT_COMPILE_PATTERN) || (answer == DELIMITER_NOT_ALPHANUMERIC))
662         {
663             if (pComments)
664             {
665                 freeArrayOfString(pComments, *nbcomments);
666             }
667
668             *nbcomments = 0;
669
670             *iErr = answer;
671             return NULL;
672         }
673
674         if ( answer == PCRE_FINISHED_OK )
675         {
676             (*nbcomments)++;
677             if (pComments == NULL)
678             {
679                 pComments = (char **)MALLOC(sizeof(char*) * (*nbcomments));
680             }
681             else
682             {
683                 pComments = (char **)REALLOC(pComments, sizeof(char*) * (*nbcomments));
684             }
685
686             if (pComments == NULL)
687             {
688                 *nbcomments = 0;
689                 *iErr = 1;
690                 return NULL;
691             }
692             pComments[(*nbcomments) - 1] = os_strdup(lines[i]);
693         }
694     }
695
696     return pComments;
697 }
698 // =============================================================================
699 static char **removeComments(const char **lines, int nbLines,
700                              const char *regexpcomments, int *newNbLines, int *iErr)
701 {
702     char **pLinesCleaned = NULL;
703
704     int i = 0;
705     *newNbLines = 0;
706
707     for (i = 0; i < nbLines; i++)
708     {
709         int Output_Start = 0;
710         int Output_End = 0;
711         pcre_error_code answer = pcre_private((char*)lines[i], (char*)regexpcomments, &Output_Start, &Output_End, NULL, NULL);
712         if ( answer == PCRE_FINISHED_OK )
713         {
714             FREE((char*)lines[i]);
715             lines[i] = NULL;
716         }
717         else
718         {
719             (*newNbLines)++;
720             if (pLinesCleaned == NULL)
721             {
722                 pLinesCleaned = (char **)MALLOC(sizeof(char*) * (*newNbLines));
723             }
724             else
725             {
726                 pLinesCleaned = (char **)REALLOC(pLinesCleaned, sizeof(char*) * (*newNbLines));
727             }
728
729             if (pLinesCleaned == NULL)
730             {
731                 *newNbLines = 0;
732                 *iErr = 1;
733                 return NULL;
734             }
735
736             pLinesCleaned[(*newNbLines) - 1] = (char*)lines[i];
737         }
738     }
739     return pLinesCleaned;
740 }
741 // =============================================================================