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