libdsproc3  2.0
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups
dsproc_csv_parser.c
Go to the documentation of this file.
1 /*******************************************************************************
2 *
3 * COPYRIGHT (C) 2013 Battelle Memorial Institute. All Rights Reserved.
4 *
5 ********************************************************************************
6 *
7 * Authors:
8 * name: Brian Ermold
9 * phone: (509) 375-2277
10 * email: brian.ermold@pnl.gov
11 *
12 ********************************************************************************
13 *
14 * REPOSITORY INFORMATION:
15 * $Revision: 71740 $
16 * $Author: ermold $
17 * $Date: 2016-06-30 19:55:27 +0000 (Thu, 30 Jun 2016) $
18 * $State:$
19 *
20 ********************************************************************************
21 *
22 * NOTE: DOXYGEN is used to generate documentation for this file.
23 *
24 *******************************************************************************/
25 
26 /** @file dsproc_csv_parser.c
27  * CSV File Parsing Functions and Utilities.
28  */
29 
30 #include "dsproc3.h"
31 
32 /*******************************************************************************
33  * Private Data and Functions
34  */
35 /** @privatesection */
36 
37 /**
38  * Private: Create the array of time column indexes in a CSVParser structure.
39  *
40  * This function must be called after the names of the time columns have
41  * been set, and the header line has been parsed.
42  *
43  * If an error occurs in this function it will be appended to the log and
44  * error mail messages, and the process status will be set appropriately.
45  *
46  * @param csv pointer to the CSVParser structure
47  *
48  * @retval 1 if successful
49  * @retval 0 if an error occurred
50  */
51 static int _csv_create_tc_index(CSVParser *csv)
52 {
53  int ti;
54  int fi;
55 
56  /* Make sure the names of the time columns have been defined */
57 
58  if (!csv->ntc || !csv->tc_names || !csv->tc_patterns) {
59 
61  "Could not create array of time column indexes\n"
62  " -> time column names have not been defined\n");
63 
65 
66  return(0);
67  }
68 
69  /* Make sure the column headers have been defined */
70 
71  if (!csv->headers) {
72 
74  "Could not create array of time column indexes\n"
75  " -> column headers have not been defined\n");
76 
78 
79  return(0);
80  }
81 
82  /* Allocate memory for the array of time column indexes */
83 
84  if (csv->tc_index) free(csv->tc_index);
85 
86  csv->tc_index = calloc(csv->ntc, sizeof(int));
87  if (!csv->tc_index) {
88 
90  "Could not create array of time column indexes\n"
91  " -> memory allocation error\n");
92 
94 
95  return(0);
96  }
97 
98  /* Get time column indexes */
99 
100  for (ti = 0; ti < csv->ntc; ++ti) {
101 
102  for (fi = 0; fi < csv->nfields; ++fi) {
103 
104  if (strcmp(csv->tc_names[ti], csv->headers[fi]) == 0) {
105  csv->tc_index[ti] = fi;
106  break;
107  }
108  }
109 
110  if (fi == csv->nfields) {
111 
113  "Could not create array of time column indexes\n"
114  " -> time column '%s' not found in header fields\n",
115  csv->tc_names[ti]);
116 
118 
119  return(0);
120  }
121  }
122 
123  return(1);
124 }
125 
126 /**
127  * Parse the time columns of a record in a CSV file and set the record time.
128  *
129  * If an error occurs in this function it will be appended to the log and
130  * error mail messages, and the process status will be set appropriately.
131  *
132  * @param csv pointer to the CSVParser structure
133  * @param record_index record index
134  *
135  * @retval 1 if successful
136  * @retval 0 if invalid time format
137  * @retval -1 if an error occurred
138  */
139 int _csv_parse_record_time(
140  CSVParser *csv,
141  int record_index)
142 {
143  char *time_string;
144  int status;
145  int tci, fi;
146  RETimeList *tc_patterns;
147  RETimeRes match;
148  RETimeRes result;
149 
150  int used_file_date;
151  timeval_t rec_time;
152  timeval_t prev_time;
153  int tro_interval;
154  double delta_t;
155  struct tm gmt;
156 
157 
158  /* Get the time column indexes */
159 
160  if (!csv->tc_index) {
161  if (!_csv_create_tc_index(csv)) {
162  return(-1);
163  }
164  }
165 
166  /* Parse time strings and merge results */
167 
168  for (tci = 0; tci < csv->ntc; ++tci) {
169 
170  fi = csv->tc_index[tci];
171  tc_patterns = csv->tc_patterns[tci];
172 
173  /* Make sure this is a valid field index */
174 
175  if (fi < 0 || fi > csv->nfields) {
176 
178  "Time column index '%d' is out of range [0, %d].\n",
179  fi, csv->nfields);
180 
182 
183  return(-1);
184  }
185 
186  time_string = csv->values[fi][record_index];
187 
188  /* Parse time string */
189 
190  status = retime_list_execute(tc_patterns, time_string, &match);
191 
192  if (status < 0) {
193 
195  "Time string pattern match failed for record %d.\n",
196  record_index + 1);
197 
199 
200  return(-1);
201  }
202  else if (status == 0) {
203 
205  "Record time format '%s' does not match '%s'\n",
206  time_string,
207  tc_patterns->retimes[tc_patterns->npatterns - 1]->tspattern);
208 
209  return(0);
210  }
211 
212  /* Merge results */
213 
214  if (tci == 0) {
215  result = match;
216  }
217  else {
218  if (match.year != -1) result.year = match.year;
219  if (match.month != -1) result.month = match.month;
220  if (match.mday != -1) result.mday = match.mday;
221  if (match.hour != -1) result.hour = match.hour;
222  if (match.min != -1) result.min = match.min;
223  if (match.sec != -1) result.sec = match.sec;
224  if (match.usec != -1) result.usec = match.usec;
225  if (match.century != -1) result.century = match.century;
226  if (match.yy != -1) result.yy = match.yy;
227  if (match.yday != -1) result.yday = match.yday;
228  if (match.secs1970 != -1) result.secs1970 = match.secs1970;
229 
230  if (match.offset.tv_sec != 0)
231  result.offset.tv_sec = match.offset.tv_sec;
232 
233  if (match.offset.tv_usec != 0)
234  result.offset.tv_usec = match.offset.tv_usec;
235  }
236  }
237 
238  /* Check if the time is already in seconds since 1970 */
239 
240  if (result.secs1970 != -1) {
241 
242  rec_time = retime_get_timeval(&result);
243  rec_time.tv_sec += csv->time_offset;
244  rec_time.tv_sec += csv->tro_offset;
245 
246  csv->tvs[record_index] = rec_time;
247 
248  return(1);
249  }
250 
251  /* Check if a base time was set by the user */
252 
253  if (csv->base_tm.tm_year) {
254 
255  result.year = csv->base_tm.tm_year + 1900;
256 
257  if (csv->base_tm.tm_mon) result.month = csv->base_tm.tm_mon + 1;
258  if (csv->base_tm.tm_mday) result.mday = csv->base_tm.tm_mday;
259  if (csv->base_tm.tm_hour) result.hour = csv->base_tm.tm_hour;
260  if (csv->base_tm.tm_min) result.min = csv->base_tm.tm_min;
261  if (csv->base_tm.tm_sec) result.sec = csv->base_tm.tm_sec;
262  }
263 
264  /* Check if we need to use the date from the file name */
265 
266  used_file_date = 0;
267 
268  if (csv->ft_patterns) {
269 
270  if (!csv->ft_result) {
271 
272  csv->ft_result = calloc(1, sizeof(RETimeRes));
273  if (!csv->ft_result) {
274 
276  "Memory allocation error creating file name RETimeRes structure\n");
277 
279 
280  return(-1);
281  }
282 
283  status = dsproc_get_csv_file_name_time(csv, csv->file_name, csv->ft_result);
284  if (status < 0) return(-1);
285  }
286 
287  if (result.year == -1) {
288 
289  if (csv->ft_result->year != -1) {
290 
291  result.year = csv->ft_result->year;
292  used_file_date = 1;
293  }
294  else {
295 
297  "Could not determine record time\n"
298  " -> year not found in record or file name time patterns\n");
299 
301 
302  return(-1);
303  }
304  }
305 
306  if (result.month == -1) {
307 
308  if (result.yday != -1) {
309 
310  yday_to_mday(result.yday,
311  &(result.year), &(result.month), &(result.mday));
312  }
313  else if (csv->ft_result->month != -1) {
314 
315  result.month = csv->ft_result->month;
316  used_file_date = 2;
317  }
318  else if (csv->ft_result->yday != -1) {
319 
321  &(result.year), &(result.month), &(result.mday));
322 
323  used_file_date = 3;
324  }
325  }
326 
327  if (result.mday == -1) {
328 
329  if (csv->ft_result->mday != -1) {
330  result.mday = csv->ft_result->mday;
331  used_file_date = 3;
332  }
333  }
334  }
335  else {
336 
337  /* Verify that the year was found */
338 
339  if (result.year == -1) {
340 
342  "Could not determine record time\n"
343  " -> year not found in record time pattern\n");
344 
346 
347  return(-1);
348  }
349  }
350 
351  rec_time = retime_get_timeval(&result);
352  rec_time.tv_sec += csv->time_offset;
353  rec_time.tv_sec += csv->tro_offset;
354 
355  /* Check for time rollovers if the file date was used */
356 
357  if (used_file_date && record_index > 0) {
358 
359  prev_time = csv->tvs[record_index - 1];
360 
361  if (TV_LT(rec_time, prev_time)) {
362 
363  switch (used_file_date) {
364 
365  case 1: /* yearly */
366 
367  gmtime_r(&(prev_time.tv_sec), &gmt);
368 
369  if (IS_LEAP_YEAR(gmt.tm_year + 1900)) {
370  tro_interval = 366 * 86400;
371  }
372  else {
373  tro_interval = 365 * 86400;
374  }
375 
376  if (!csv->tro_threshold) {
377  csv->tro_threshold = 86400;
378  }
379 
380  break;
381 
382  case 2: /* monthly */
383 
384  gmtime_r(&(prev_time.tv_sec), &gmt);
385 
386  tro_interval = days_in_month(gmt.tm_year + 1900, gmt.tm_mon + 1)
387  * 86400;
388 
389  if (!csv->tro_threshold) {
390  csv->tro_threshold = 43200;
391  }
392 
393  break;
394 
395  default: /* daily */
396 
397  tro_interval = 86400;
398 
399  if (!csv->tro_threshold) {
400  csv->tro_threshold = 3600;
401  }
402  }
403 
404  delta_t = (TV_DOUBLE(rec_time) + tro_interval) - TV_DOUBLE(prev_time);
405 
406  if ((delta_t > 0) && (delta_t < csv->tro_threshold)) {
407 
408  rec_time.tv_sec += tro_interval;
409  csv->tro_offset += tro_interval;
410  }
411  }
412  }
413 
414  csv->tvs[record_index] = rec_time;
415 
416  return(1);
417 }
418 
419 /**
420  * Private: Allocate memory for the header and field pointers.
421  *
422  * @param csv pointer to the CSVParser structure
423  * @param nfields number of fields
424  * @param nrecs number of records
425  *
426  * @retval 1 if successful
427  * @retval 0 if a memory allocation error occurs
428  */
429 static int _csv_realloc_data(CSVParser *csv, int nfields, int nrecs)
430 {
431  int prev_nfields = csv->nfields_alloced;
432  int prev_nrecs = csv->nrecs_alloced;
433  size_t nelems;
434  int fi;
435 
436  /* Increase the maximum number of records if necessary */
437 
438  if (nrecs > prev_nrecs) {
439 
440  nelems = nrecs - prev_nrecs;
441 
442  /* realloc memory for record values */
443 
444  for (fi = 0; fi < prev_nfields; ++fi) {
445 
446  csv->values[fi] = (char **)realloc(csv->values[fi], nrecs * sizeof(char *));
447  if (!csv->values[fi]) return(0);
448 
449  memset(csv->values[fi] + prev_nrecs, 0, nelems * sizeof(char *));
450  }
451 
452  /* realloc memory for time vector if applicable */
453 
454  if (csv->ntc) {
455 
456  csv->tvs = (timeval_t *)realloc(csv->tvs, nrecs * sizeof(timeval_t));
457  if (!csv->tvs) return(0);
458 
459  memset(csv->tvs + prev_nrecs, 0, nelems * sizeof(timeval_t));
460  }
461 
462  csv->nrecs_alloced = nrecs;
463  }
464 
465  /* Increase the maximum number of fields if necessary */
466 
467  if (nfields > prev_nfields) {
468 
469  nelems = nfields - prev_nfields;
470 
471  /* realloc memory for record buffer */
472 
473  csv->rec_buff = (char **)realloc(csv->rec_buff, nfields * sizeof(char *));
474  if (!csv->rec_buff) return(0);
475 
476  /* realloc memory for header pointers */
477 
478  csv->headers = (char **)realloc(csv->headers, nfields * sizeof(char *));
479  if (!csv->headers) return(0);
480 
481  memset(csv->headers + prev_nfields, 0, nelems * sizeof(char *));
482 
483  /* realloc memory for free_header flags */
484 
485  csv->free_header = (int *)realloc(csv->free_header, nfields * sizeof(int));
486  if (!csv->free_header) return(0);
487 
488  memset(csv->free_header + prev_nfields, 0, nelems * sizeof(int));
489 
490  /* realloc memory for additional field columns and values */
491 
492  csv->values = (char ***)realloc(csv->values, nfields * sizeof(char **));
493  if (!csv->values) return(0);
494 
495  for (fi = prev_nfields; fi < nfields; ++fi) {
496 
497  csv->values[fi] = (char **)calloc(csv->nrecs_alloced, sizeof(char *));
498  if (!csv->values[fi]) return(0);
499  }
500 
501  csv->nfields_alloced = nfields;
502  }
503 
504  return(1);
505 }
506 
507 /*******************************************************************************
508  * Public Functions
509  */
510 /** @publicsection */
511 
512 /**
513  * Free all memory used by a CSVParser structure.
514  *
515  * @param csv pointer to the CSVParser structure
516  */
518 {
519  int i;
520 
521  if (csv) {
522 
523  if (csv->file_name) free(csv->file_name);
524  if (csv->file_path) free(csv->file_path);
525  if (csv->file_data) free(csv->file_data);
526  if (csv->lines) free(csv->lines);
527 
528  if (csv->headers && csv->free_header) {
529  for (i = 0; i < csv->nfields_alloced; ++i) {
530  if (csv->free_header[i]) free(csv->headers[i]);
531  }
532  }
533 
534  if (csv->header_data) free(csv->header_data);
535  if (csv->headers) free(csv->headers);
536  if (csv->free_header) free(csv->free_header);
537 
538  if (csv->rec_buff) free(csv->rec_buff);
539 
540  if (csv->values) {
541  for (i = 0; i < csv->nfields_alloced; ++i) {
542  free(csv->values[i]);
543  }
544  free(csv->values);
545  }
546 
547  if (csv->ft_patterns) {
549  }
550 
551  if (csv->ft_result) {
552  free(csv->ft_result);
553  }
554 
555  if (csv->tc_names) {
556  for (i = 0; i < csv->ntc; ++i) {
557  free(csv->tc_names[i]);
558  }
559  free(csv->tc_names);
560  }
561 
562  if (csv->tc_patterns) {
563  for (i = 0; i < csv->ntc; ++i) {
564  retime_list_free(csv->tc_patterns[i]);
565  }
566  free(csv->tc_patterns);
567  }
568 
569  if (csv->tc_index) free(csv->tc_index);
570 
571  if (csv->tvs) free(csv->tvs);
572 
573  free(csv);
574  }
575 }
576 
577 /**
578  * Get the time from a CSV file name.
579  *
580  * This function must be called after the CSVParser structure has been
581  * initialized using the dsproc_init_csv_parser() function and the file name
582  * time string patterns have been set using dsproc_set_csv_file_time_patterns().
583  *
584  * If an error occurs in this function it will be appended to the log and
585  * error mail messages, and the process status will be set appropriately.
586  *
587  * @param csv pointer to the CSVParser structure
588  * @param name CSV file name
589  * @param result if not NULL the complete result from the pattern match
590  * will be stored in this RETimeRes structure.
591  *
592  * @retval time time in seconds since 1970
593  * @retval -1 if an error occurred
594  */
596  CSVParser *csv,
597  const char *name,
598  RETimeRes *result)
599 {
600  RETimeRes result_buffer;
601  RETimeRes *resultp;
602  int status;
603  time_t secs1970;
604 
605  resultp = (result) ? result : &result_buffer;
606 
607  if (!csv->ft_patterns) {
608 
610  "Could not get time from CSV file name: %s\n"
611  " -> no time string patterns have been defined\n",
612  name);
613 
615 
616  return(-1);
617  }
618 
619  status = retime_list_execute(csv->ft_patterns, name, resultp);
620 
621  if (status < 0) {
622 
624  "Could not get time from CSV file name: %s\n"
625  " -> time string pattern matching error occured\n",
626  name);
627 
629 
630  return(-1);
631  }
632  else if (status == 0) {
633 
635  "Could not get time from CSV file name: %s\n"
636  " -> file name format does not match time string pattern: '%s'\n",
637  name,
639 
641 
642  return(-1);
643  }
644 
645  secs1970 = retime_get_secs1970(resultp);
646 
647  if (secs1970 == -1) {
648 
650  "Could not get time from CSV file name: %s\n"
651  " -> year not found in time string pattern\n",
652  name);
653 
655 
656  return(-1);
657  }
658 
659  return(secs1970);
660 }
661 
662 /**
663  * Get the array of string pointers to the column headers in a CSV file.
664  *
665  * The memory used by the returned array of strings belongs to the
666  * CSVParser structure and must not be freed by the calling process.
667  *
668  * @param csv pointer to the CSVParser structure
669  * @param nfields output: number of header fields.
670  *
671  * @retval headers array of string pointers to the column headers
672  * @retval NULL if the column headers have not been parsed
673  */
674 char **dsproc_get_csv_column_headers(CSVParser *csv, int *nfields)
675 {
676  if (nfields) *nfields = csv->nfields;
677  if (csv->nfields > 0) return(csv->headers);
678  return((char **)NULL);
679 }
680 
681 /**
682  * Get the array of string pointers for a field in a CSV file.
683  *
684  * The memory used by the returned array of strings belongs to the
685  * CSVParser structure and must not be freed by the calling process.
686  *
687  * @param csv pointer to the CSVParser structure
688  * @param name column name as specified in the header.
689  *
690  * @retval vals array of string pointers to the column values
691  * @retval NULL if the column name was not found
692  */
693 char **dsproc_get_csv_field_strvals(CSVParser *csv, const char *name)
694 {
695  int fi;
696 
697  for (fi = 0; fi < csv->nfields; ++fi) {
698 
699  if (strcmp(csv->headers[fi], name) == 0) {
700  return((char **)csv->values[fi]);
701  }
702  }
703 
704  return((char **)NULL);
705 }
706 
707 /**
708  * Get the array of record times after parsing a CSV file.
709  *
710  * The memory used by the returned array of timevals belongs to the
711  * CSVParser structure and must not be freed by the calling process.
712  *
713  * @param csv pointer to the CSVParser structure
714  * @param nrecs output: the number of records found in the file
715  *
716  * @retval times array of timevals in seconds since 1970
717  * @retval NULL if no records have been parsed from the file
718  */
720 {
721  if (nrecs) *nrecs = csv->nrecs;
722  if (csv->nrecs) return(csv->tvs);
723  return((timeval_t *)NULL);
724 }
725 
726 /**
727  * Get the next line from the file loaded into the CSVParser structure.
728  *
729  * The memory used by the returned line belongs to the CSVParser structure
730  * and must not be freed by the calling process.
731  *
732  * @param csv pointer to the CSVParser structure
733  *
734  * @retval line the next line in the file loaded into memory
735  * @retval NULL if the end of file was reached
736  */
738 {
739  if (csv->linenum >= csv->nlines) {
740  return((char *)NULL);
741  }
742 
743  csv->linep = csv->lines[csv->linenum];
744  csv->linenum += 1;
745 
746  return(csv->linep);
747 }
748 
749 /**
750  * Initialize a CSVParser structure.
751  *
752  * If an error occurs in this function it will be appended to the log and
753  * error mail messages, and the process status will be set appropriately.
754  *
755  * @param csv pointer to the CSV Data structure to initialize,
756  * or NULL to create a new CSVParser structure.
757  *
758  * @retval csv pointer to the CSVParser structure
759  * @retval NULL if an error occurred
760  */
762 {
763  if (csv) {
764 
765  /* Initialize an existing CSVParser structure */
766 
767  if (csv->file_path) {
768  free(csv->file_path);
769  csv->file_path = (char *)NULL;
770  }
771 
772  if (csv->file_name) {
773  free(csv->file_name);
774  csv->file_name = (char *)NULL;
775  }
776 
777  csv->nlines = 0;
778  csv->linep = (char *)NULL;
779  csv->linenum = 0;
780 
781  csv->nfields = 0;
782  csv->nrecs = 0;
783 
784  if (csv->tc_index) {
785  free(csv->tc_index);
786  csv->tc_index = (int *)NULL;
787  }
788 
789  csv->tro_offset = 0;
790  }
791  else {
792 
793  /* Create a new CSVParser structure */
794 
795  csv = (CSVParser *)calloc(1, sizeof(CSVParser));
796  if (!csv) {
797 
799  "Memory allocation error loading CSV file: %s\n",
800  csv->file_name);
801 
803 
804  return((CSVParser *)NULL);
805  }
806 
807  csv->delim = ',';
808  csv->nlines_guess = 4096;
809  csv->nfields_guess = 32;
810  }
811 
812  return(csv);
813 }
814 
815 /**
816  * Load a CSV data file into a CSVParser structure.
817  *
818  * If an error occurs in this function it will be appended to the log and
819  * error mail messages, and the process status will be set appropriately.
820  *
821  * @param csv pointer to the CSVParser structure created by dsproc_init_csv_parser()
822  * @param path path to the location of the file
823  * @param name the name of the file
824  *
825  * @retval nlines number of lines read from the file
826  * @retval -1 if an error occurred
827  */
828 int dsproc_load_csv_file(CSVParser *csv, const char *path, const char *name)
829 {
830  char full_path[PATH_MAX];
831  size_t nread;
832  size_t nbytes;
833  FILE *fp;
834  char *chrp;
835  int nlines;
836  int li;
837 
838  /* Initialize the CSVParser structure if necessary */
839 
840  if (csv->nlines != 0) {
841  if (!dsproc_init_csv_parser(csv)) {
842  return(-1);
843  }
844  }
845 
846  /* Initialize the array of CSVParser line pointers if necessary */
847 
848  if (csv->nlines_alloced == 0) {
849 
850  nlines = csv->nlines_guess;
851 
852  csv->lines = (char **)malloc(nlines * sizeof(char *));
853 
854  if (!csv->lines) {
855 
857  "Memory allocation error loading CSV file: %s\n",
858  csv->file_name);
859 
861 
862  return(-1);
863  }
864 
865  csv->nlines_alloced = nlines;
866  }
867 
868  /* Set the file name and path in the CSVParser structure */
869 
870  if (!(csv->file_path = strdup(path)) ||
871  !(csv->file_name = strdup(name))) {
872 
874  "Memory allocation error loading CSV file: %s\n",
875  csv->file_name);
876 
878 
879  return(-1);
880  }
881 
882  snprintf(full_path, PATH_MAX, "%s/%s", path, name);
883 
884  if (csv->ft_result) {
885  free(csv->ft_result);
886  csv->ft_result = (RETimeRes *)NULL;
887  }
888 
889  /* Get the CSV file status */
890 
891  if (stat(full_path, &csv->file_stats) < 0) {
892 
894  "Could not get file status for: %s\n"
895  " -> %s\n", full_path, strerror(errno));
896 
898 
899  return(-1);
900  }
901 
902  /* Read in the entire CSV file */
903 
904  nbytes = csv->file_stats.st_size;
905  if (nbytes == 0) return(0);
906 
907  if (nbytes > (size_t)csv->nbytes_alloced) {
908 
909  csv->file_data = (char *)realloc(csv->file_data, (nbytes + 1) * sizeof(char));
910  if (!csv->file_data) {
911 
913  "Memory allocation error loading CSV file: %s\n",
914  csv->file_name);
915 
917 
918  return(-1);
919  }
920 
921  csv->nbytes_alloced = nbytes;
922  }
923 
924  fp = fopen(full_path, "r");
925  if (!fp) {
926 
928  "Could not open file: %s\n"
929  " -> %s\n", csv->file_name, strerror(errno));
930 
932 
933  return(-1);
934  }
935 
936  nread = fread(csv->file_data, 1, nbytes, fp);
937  fclose(fp);
938 
939  if (nread != nbytes) {
940 
942  "Could not read CSV file: %s\n"
943  " -> %s\n", csv->file_name, strerror(errno));
944 
946 
947  return(-1);
948  }
949 
950  csv->file_data[nbytes] = '\0';
951 
952  /* Set the line pointers */
953 
954  chrp = csv->file_data;
955  csv->lines[0] = chrp;
956 
957  li = 1;
958  while ((chrp = dsproc_find_csv_delim(chrp, '\n'))) {
959 
960  if (chrp != csv->file_data) {
961 
962  /* Handle carriage returns */
963 
964  if (*(chrp - 1) == '\r') {
965  *(chrp - 1) = '\0';
966  }
967  }
968 
969  *chrp++ = '\0';
970  if (*chrp == '\0') break; // end of file
971 
972  if (li >= csv->nlines_alloced) {
973 
974  /* Estimate the total number of lines we will need:
975  *
976  * = (sizeof_file / nbytes_read) * nlines_read
977  */
978 
979  nlines = (nbytes / (chrp - csv->file_data)) * li
980  + csv->nlines_guess;
981 
982  csv->lines = (char **)realloc(csv->lines, nlines * sizeof(char *));
983 
984  if (!csv->lines) {
985 
987  "Memory allocation error loading CSV file: %s\n",
988  csv->file_name);
989 
991 
992  return(-1);
993  }
994 
995  csv->nlines_alloced = nlines;
996  }
997 
998  csv->lines[li++] = chrp;
999  }
1000 
1001  csv->nlines = li;
1002 
1003  return(csv->nlines);
1004 }
1005 
1006 /**
1007  * Parse a header line.
1008  *
1009  * If an error occurs in this function it will be appended to the log and
1010  * error mail messages, and the process status will be set appropriately.
1011  *
1012  * @param csv pointer to the CSVParser structure
1013  * @param linep pointer to the line to parse,
1014  * or NULL to parse the current line.
1015  *
1016  * @retval nfields number of header fields
1017  * @retval -1 if a memory allocation error occurs
1018  */
1019 int dsproc_parse_csv_header(CSVParser *csv, const char *linep)
1020 {
1021  char delim = csv->delim;
1022  int nfields;
1023  int count;
1024  int fi;
1025 
1026  /* Get pointer to the current line if linep is NULL */
1027 
1028  if (!linep) {
1029 
1030  if (csv->linenum == 0) {
1031  csv->linenum += 1;
1032  }
1033 
1034  linep = csv->lines[csv->linenum - 1];
1035  }
1036 
1037  /* Count the number of header fields */
1038 
1039  count = dsproc_count_csv_delims(linep, delim) + 1;
1040 
1041  /* Make a copy of the header line */
1042 
1043  if (csv->header_data) free(csv->header_data);
1044 
1045  csv->header_data = strdup(linep);
1046 
1047  if (!csv->header_data) {
1048 
1050  "Memory allocation error parsing CSV header for file: %s\n",
1051  csv->file_name);
1052 
1054 
1055  return(-1);
1056  }
1057 
1058  /* Allocate memory for the header and field pointers */
1059 
1060  if (!_csv_realloc_data(csv, count, csv->nlines)) {
1061 
1063  "Memory allocation error parsing CSV header for file: %s\n",
1064  csv->file_name);
1065 
1067 
1068  return(-1);
1069  }
1070 
1071  /* Free any header strings that have been added manually */
1072 
1073  if (csv->headers && csv->free_header) {
1074  for (fi = 0; fi < csv->nfields_alloced; ++fi) {
1075  if (csv->free_header[fi]) {
1076  free(csv->headers[fi]);
1077  csv->headers[fi] = (char *)NULL;
1078  csv->free_header[fi] = 0;
1079  }
1080  }
1081  }
1082 
1083  /* Parse the header line */
1084 
1085  nfields = dsproc_split_csv_string(csv->header_data, delim, count, csv->headers);
1086 
1087  if (nfields != count) {
1088 
1089  if (nfields == 0) {
1090  /* input line was zero length string or all white-space */
1091  return(0);
1092  }
1093 
1094  /* This should never happen */
1095 
1097  "Unknown error parsing CSV header line for file: %s\n",
1098  csv->file_name);
1099 
1101 
1102  return(-1);
1103  }
1104 
1105  csv->nfields = nfields;
1106 
1107  return(nfields);
1108 }
1109 
1110 /**
1111  * Parse a record line.
1112  *
1113  * If the number of values found on the current line does not match
1114  * the number of header fields, a warning message will be generated.
1115  *
1116  * The records values in the internal CSVParser structure point to the
1117  * substrings in the input string and have been null terminated. All
1118  * leading and trailing spaces, and surrounding quotes have also been
1119  * removed from the substrings.
1120  *
1121  * The input string must not be altered or freed after calling this function
1122  * until the record values are no longer needed by the calling process.
1123  *
1124  * @param csv pointer to the CSVParser structure
1125  * @param linep pointer to the line to parse, or NULL to parse
1126  * from the beginning of the current line.
1127  * @param flags reserved for control flags, set to 0 to maintain
1128  * backward compatibility with future updates.
1129  *
1130  * @retval 1 if successful
1131  * @retval 0 if the record time has an invalid format, or the number of values
1132  * found on the line does not match the number of header fields
1133  * @retval -1 if an error occurred
1134  */
1135 int dsproc_parse_csv_record(CSVParser *csv, char *linep, int flags)
1136 {
1137  char delim = csv->delim;
1138  int nfields;
1139  int fi;
1140  int status;
1141 
1142  /* prevent unused parameter warning */
1143 
1144  flags = flags;
1145 
1146  /* Get pointer to the current line if linep is NULL */
1147 
1148  if (!linep) {
1149 
1150  if (csv->linenum == 0) {
1151  csv->linenum += 1;
1152  }
1153 
1154  linep = csv->lines[csv->linenum - 1];
1155  }
1156 
1157  /* Check size of csv->nrecs_alloced */
1158 
1159  if (csv->nrecs == csv->nrecs_alloced) {
1160 
1161  if (!_csv_realloc_data(csv, csv->nfields, csv->nrecs * 1.5)) {
1162 
1164  "Memory allocation error parsing CSV record #%d for file: %s\n",
1165  csv->nrecs + 1, csv->file_name);
1166 
1168 
1169  return(-1);
1170  }
1171  }
1172 
1173  /* Parse the record line */
1174 
1175  nfields = dsproc_split_csv_string(linep, delim, csv->nfields, csv->rec_buff);
1176 
1177  /* Make sure the number of fields in the record
1178  * matches the number of header fields. */
1179 
1180  if (nfields != csv->nfields) {
1181 
1183  "Expected %d values but found %d\n",
1184  csv->nfields, nfields);
1185 
1186  return(0);
1187  }
1188 
1189  /* Set the record value pointers */
1190 
1191  for (fi = 0; fi < nfields; ++fi) {
1192  csv->values[fi][csv->nrecs] = csv->rec_buff[fi];
1193  }
1194 
1195  /* Get the record time if a time format string was specified. */
1196 
1197  if (csv->ntc) {
1198 
1199  status = _csv_parse_record_time(csv, csv->nrecs);
1200  if (status < 0) return(-1);
1201  if (status == 0) return(0);
1202  }
1203 
1204  csv->nrecs += 1;
1205 
1206  return(1);
1207 }
1208 
1209 /**
1210  * Set the column delimiter.
1211  *
1212  * @param csv pointer to the CSVParser structure
1213  * @param delim column delimiter
1214  *
1215  */
1216 void dsproc_set_csv_delimiter(CSVParser *csv, char delim)
1217 {
1218  csv->delim = delim;
1219 }
1220 
1221 /**
1222  * Set or change a column name in the header.
1223  *
1224  * If an error occurs in this function it will be appended to the log and
1225  * error mail messages, and the process status will be set appropriately.
1226  *
1227  * @param csv pointer to the CSVParser structure
1228  * @param index column index
1229  * @param name column name
1230  *
1231  * @retval 1 if successful
1232  * @retval 0 if a memory allocation error occurs
1233  */
1234 int dsproc_set_csv_column_name(CSVParser *csv, int index, const char *name)
1235 {
1236  int max_fields;
1237 
1238  /* Allocate/reallocate memory if necessary */
1239 
1240  max_fields = csv->nfields_guess;
1241  while (max_fields <= index) max_fields += csv->nfields_guess;
1242 
1243  if (!_csv_realloc_data(csv, max_fields, csv->nlines)) {
1244 
1246  "Memory allocation error adding CSV column name:\n"
1247  " -> file name: %s\n"
1248  " -> column index: %d\n"
1249  " -> column name: %s\n",
1250  csv->file_name, index, name);
1251 
1253 
1254  return(0);
1255  }
1256 
1257  if (csv->headers[index] && csv->free_header[index]) {
1258  free(csv->headers[index]);
1259  }
1260 
1261  csv->headers[index] = strdup(name);
1262  if (!csv->headers[index]) {
1263 
1265  "Memory allocation error adding CSV column name:\n"
1266  " -> file name: %s\n"
1267  " -> column index: %d\n"
1268  " -> column name: %s\n",
1269  csv->file_name, index, name);
1270 
1272 
1273  return(0);
1274  }
1275 
1276  csv->free_header[index] = 1;
1277 
1278  if (csv->nfields < index + 1) {
1279  csv->nfields = index + 1;
1280  }
1281 
1282  return(1);
1283 }
1284 
1285 /**
1286  * Set the base time to use for record times.
1287  *
1288  * This option is used when the records times are relative to a
1289  * base time.
1290  *
1291  * @param csv pointer to the CSVParser structure
1292  * @param base_time base time in seconds since 1970
1293  *
1294  * @retval 1 if successful
1295  * @retval 0 if an error occurred
1296  */
1297 int dsproc_set_csv_base_time(CSVParser *csv, time_t base_time)
1298 {
1299  memset(&(csv->base_tm), 0, sizeof(struct tm));
1300 
1301  if (!gmtime_r(&base_time, &(csv->base_tm))) {
1302 
1304  "Could not set base time for CSV data records\n"
1305  " -> gmtime error: %s\n",
1306  strerror(errno));
1307 
1309  return(0);
1310  }
1311 
1312  return(1);
1313 }
1314 
1315 /**
1316  * Specify the pattern to use to parse the date/time from the file name.
1317  *
1318  * The time string patterns can containing a mixture of regex and time format
1319  * codes similar to the strptime function. The time format codes recognized by
1320  * this function begin with a % and are followed by one of the following characters:
1321  *
1322  * - 'C' century number (year/100) as a 2-digit integer
1323  * - 'd' day number in the month (1-31).
1324  * - 'e' day number in the month (1-31).
1325  * - 'h' hour * 100 + minute (0-2359)
1326  * - 'H' hour (0-23)
1327  * - 'j' day number in the year (1-366).
1328  * - 'm' month number (1-12)
1329  * - 'M' minute (0-59)
1330  * - 'n' arbitrary whitespace
1331  * - 'o' time offset in seconds
1332  * - 's' seconds since Epoch, 1970-01-01 00:00:00 +0000 (UTC)
1333  * - 'S' second (0-60; 60 may occur for leap seconds)
1334  * - 't' arbitrary whitespace
1335  * - 'y' year within century (0-99)
1336  * - 'Y' year with century as a 4-digit integer
1337  * - '%' a literal "%" character
1338  *
1339  * An optional 0 character can be used between the % and format code to
1340  * specify that the number must be zero padded. For example, '%0d' specifies
1341  * that the day range is 01 to 31.
1342  *
1343  * See the regex(7) man page for the descriptions of the regex patterns.
1344  *
1345  * If an error occurs in this function it will be appended to the log and
1346  * error mail messages, and the process status will be set appropriately.
1347  *
1348  * @param csv pointer to the CSVParser structure
1349  * @param npatterns number of possible patterns to check
1350  * @param patterns list of possible time string patterns
1351  *
1352  * @retval 1 if successful
1353  * @retval 0 if an error occurred
1354  */
1356  CSVParser *csv,
1357  int npatterns,
1358  const char **patterns)
1359 {
1360  if (csv->ft_patterns) {
1362  }
1363 
1364  csv->ft_patterns = retime_list_compile(npatterns, patterns, 0);
1365 
1366  if (!csv->ft_patterns) {
1367 
1369  "Could not compile CSV file time pattern(s)\n");
1370 
1372 
1373  return(0);
1374  }
1375 
1376  return(1);
1377 }
1378 
1379 /**
1380  * Set the time offset to apply to record times.
1381  *
1382  * @param csv pointer to the CSVParser structure
1383  * @param time_offset base time in seconds since 1970
1384  */
1385 void dsproc_set_csv_time_offset(CSVParser *csv, time_t time_offset)
1386 {
1387  csv->time_offset = time_offset;
1388 }
1389 
1390 /**
1391  * Specify the pattern to use to parse a date/time column.
1392  *
1393  * The time string patterns can containing a mixture of regex and time format
1394  * codes similar to the strptime function. The time format codes recognized by
1395  * this function begin with a % and are followed by one of the following characters:
1396  *
1397  * - 'C' century number (year/100) as a 2-digit integer
1398  * - 'd' day number in the month (1-31).
1399  * - 'e' day number in the month (1-31).
1400  * - 'h' hour * 100 + minute (0-2359)
1401  * - 'H' hour (0-23)
1402  * - 'j' day number in the year (1-366).
1403  * - 'm' month number (1-12)
1404  * - 'M' minute (0-59)
1405  * - 'n' arbitrary whitespace
1406  * - 's' seconds since Epoch, 1970-01-01 00:00:00 +0000 (UTC)
1407  * - 'S' second (0-60; 60 may occur for leap seconds)
1408  * - 't' arbitrary whitespace
1409  * - 'y' year within century (0-99)
1410  * - 'Y' year with century as a 4-digit integer
1411  * - '%' a literal "%" character
1412  *
1413  * An optional 0 character can be used between the % and format code to
1414  * specify that the number must be zero padded. For example, '%0d' specifies
1415  * that the day range is 01 to 31.
1416  *
1417  * See the regex(7) man page for the descriptions of the regex patterns.
1418  *
1419  * If an error occurs in this function it will be appended to the log and
1420  * error mail messages, and the process status will be set appropriately.
1421  *
1422  * @param csv pointer to the CSVParser structure
1423  * @param name name of the time column
1424  * @param npatterns number of possible patterns to check
1425  * @param patterns list of possible time string patterns
1426  *
1427  * @retval 1 if successful
1428  * @retval 0 if an error occurred
1429  */
1431  CSVParser *csv,
1432  const char *name,
1433  int npatterns,
1434  const char **patterns)
1435 {
1436  char **tc_names;
1437  RETimeList **tc_patterns;
1438  int ncols;
1439  int tci;
1440 
1441  ncols = csv->ntc;
1442 
1443  /* Clear the array of time column indexes if it has already been created */
1444 
1445  if (csv->tc_index) {
1446  free(csv->tc_index);
1447  csv->tc_index = (int *)NULL;
1448  }
1449 
1450  /* Check if an entry already exists for this time column */
1451 
1452  for (tci = 0; tci < ncols; ++tci) {
1453  if (strcmp(csv->tc_names[tci], name) == 0) {
1454  break;
1455  }
1456  }
1457 
1458  if (tci == ncols) {
1459 
1460  /* Allocate memory for a new entry */
1461 
1462  tc_names = (char **)realloc(csv->tc_names, (ncols + 1) * sizeof(char *));
1463  if (!tc_names) goto MEMORY_ERROR;
1464 
1465  csv->tc_names = tc_names;
1466 
1467  tc_patterns = (RETimeList **)realloc(csv->tc_patterns, (ncols + 1) * sizeof(RETimeList *));
1468  if (!tc_patterns) goto MEMORY_ERROR;
1469 
1470  csv->tc_patterns = tc_patterns;
1471 
1472  /* Set the time column name */
1473 
1474  csv->tc_names[tci] = strdup(name);
1475  if (!csv->tc_names[tci]) goto MEMORY_ERROR;
1476 
1477  csv->ntc += 1;
1478  }
1479  else {
1480  retime_list_free(csv->tc_patterns[tci]);
1481  }
1482 
1483  /* Compile the list of time string patterns */
1484 
1485  csv->tc_patterns[tci] = retime_list_compile(npatterns, patterns, 0);
1486  if (!csv->tc_patterns[tci]) {
1487  goto MEMORY_ERROR;
1488  }
1489 
1490  return(1);
1491 
1492 MEMORY_ERROR:
1493 
1495  "Memory allocation error adding CSV time column patterns\n");
1496 
1498 
1499  return(0);
1500 }
1501 
1502 /*******************************************************************************
1503  * Parsing Utilities
1504  */
1505 
1506 /**
1507  * Count the number of delimiters in a string.
1508  *
1509  * Delimiters inside single or double quoted strings will not be matched.
1510  *
1511  * @param strp pointer to the delimited string
1512  * @param delim delimiter character
1513  *
1514  * @retval nfound number of delimiters found.
1515  */
1516 int dsproc_count_csv_delims(const char *strp, char delim)
1517 {
1518  int nfound = 0;
1519 
1520  strp = dsproc_skip_csv_whitespace(strp, delim);
1521 
1522  while ((strp = dsproc_find_csv_delim(strp, delim))) {
1523  ++strp;
1524  ++nfound;
1525  strp = dsproc_skip_csv_whitespace(strp, delim);
1526  }
1527 
1528  return(nfound);
1529 }
1530 
1531 /**
1532  * Find the next delimiter in a string.
1533  *
1534  * Delimiters inside single or double quoted strings will not be matched.
1535  *
1536  * @param strp pointer to the string
1537  * @param delim delimiter to search for
1538  *
1539  * @retval strp pointer to the next delimiter in the string
1540  * @retval NULL if not found
1541  */
1542 char *dsproc_find_csv_delim(const char *strp, char delim)
1543 {
1544  char quote = '\0';
1545 
1546  for (; *strp != '\0'; ++strp) {
1547 
1548  if (quote) {
1549  if (*strp == quote) {
1550  quote = '\0';
1551  }
1552  }
1553  else if (*strp == delim) {
1554  return((char *)strp);
1555  }
1556  else if (*strp == '"' || *strp == '\'') {
1557  quote = *strp;
1558  }
1559  }
1560 
1561  return((char *)NULL);
1562 }
1563 
1564 /**
1565  * Skip white-space characters that do not match the delimiter.
1566  *
1567  * If a delimiter is specified, it will not be treated as a
1568  * white-space character unless it is a normal space ' '.
1569  *
1570  * @param strp pointer to the first character in the string
1571  * @param delim delimiter character
1572  *
1573  * @retval strp pointer to the next non white-space character
1574  */
1575 char *dsproc_skip_csv_whitespace(const char *strp, char delim)
1576 {
1577  if (delim && delim != ' ') {
1578  while (isspace(*strp) && *strp != delim) ++strp;
1579  }
1580  else {
1581  while (isspace(*strp)) ++strp;
1582  }
1583 
1584  return((char *)strp);
1585 }
1586 
1587 /**
1588  * Split a delimited string into list of strings.
1589  *
1590  * The pointers in the output list point to the substrings in the input
1591  * string and have been null terminated. All leading and trailing spaces,
1592  * and surrounding quotes have also been removed from the substrings.
1593  *
1594  * The pointers in the output list will only be valid as long as the
1595  * memory used by the input string is not altered or freed.
1596  *
1597  * @param strp pointer to the delimited string
1598  * @param delim delimiter character
1599  * @param length length of the output list
1600  * @param list output: pointers to the substrings in the input string.
1601  *
1602  * @retval nfound number of substrings returned in the output list,
1603  * or the number that would have beed returned if
1604  * the output list had been big enough.
1605  */
1606 int dsproc_split_csv_string(char *strp, char delim, int length, char **list)
1607 {
1608  char *startp;
1609  char *endp;
1610  char *delimp;
1611  int nfound;
1612 
1613  /* Skip leading white-space */
1614 
1615  startp = dsproc_skip_csv_whitespace(strp, delim);
1616  if (*startp == '\0') return(0);
1617 
1618  nfound = 0;
1619 
1620  while ((delimp = dsproc_find_csv_delim(startp, delim))) {
1621 
1622  if (delimp != startp) {
1623 
1624  /* Trim trailing white-space from the substring */
1625 
1626  endp = delimp - 1;
1627  while (endp > startp && isspace(*endp)) *endp-- = '\0';
1628 
1629  /* Remove surrounding quotes from the substring */
1630 
1631  if (endp > startp) {
1632  if (*endp == '"' || *endp == '\'') {
1633  if (*startp == *endp) {
1634  startp += 1;
1635  *endp = '\0';
1636  }
1637  }
1638  }
1639  }
1640 
1641  /* Terminate field */
1642 
1643  *delimp = '\0';
1644 
1645  /* Set substring pointer in output list */
1646 
1647  if (nfound < length) {
1648  list[nfound] = startp;
1649  }
1650 
1651  nfound += 1;
1652 
1653  /* Skip leading white-space in the next substring */
1654 
1655  startp = dsproc_skip_csv_whitespace(delimp+1, delim);
1656  }
1657 
1658  /* Trim trailing white-space from the last field */
1659 
1660  endp = startp + strlen(startp) - 1;
1661  while (endp > startp && isspace(*endp)) *endp-- = '\0';
1662 
1663  /* Remove surrounding quotes from the substring */
1664 
1665  if (endp > startp) {
1666  if (*endp == '"' || *endp == '\'') {
1667  if (*startp == *endp) {
1668  startp += 1;
1669  *endp = '\0';
1670  }
1671  }
1672  }
1673 
1674  if (nfound < length) {
1675  list[nfound] = startp;
1676  }
1677 
1678  nfound += 1;
1679 
1680  return(nfound);
1681 }
1682 
1683 /*******************************************************************************
1684  * Print Functions
1685  */
1686 
1687 /**
1688  * Print CSV header and records.
1689  *
1690  * If the number of values found on the current line does not match
1691  * the number of header fields, a warning message will be generated.
1692  *
1693  * @param fp pointer to the output stream
1694  * @param csv pointer to the CSVParser structure
1695  *
1696  * @retval 1 if successful
1697  * @retval 0 if no data was found in the CSV table
1698  * @retval -1 if a write error occurred
1699  */
1700 int dsproc_print_csv(FILE *fp, CSVParser *csv)
1701 {
1702  int status;
1703 
1704  status = dsproc_print_csv_header(fp, csv);
1705  if (status != 1) return(status);
1706 
1707  fprintf(fp, "\n");
1708 
1709  status = dsproc_print_csv_record(fp, csv);
1710  if (status != 1) return(status);
1711 
1712  return(1);
1713 }
1714 
1715 /**
1716  * Print CSV header data.
1717  *
1718  * If the number of values found on the current line does not match
1719  * the number of header fields, a warning message will be generated.
1720  *
1721  * @param fp pointer to the output stream
1722  * @param csv pointer to the CSVParser structure
1723  *
1724  * @retval 1 if successful
1725  * @retval 0 if no header was found in the CSV table
1726  * @retval -1 if a write error occurred
1727  */
1729 {
1730  char delim = csv->delim;
1731  int fi;
1732 
1733  if (csv->nfields <= 0) {
1734  fprintf(fp, "No header stored in CSV Table\n");
1735  return(0);
1736  }
1737 
1738  fprintf(fp, "%s", csv->headers[0]);
1739  for (fi = 1; fi < csv->nfields; ++fi) {
1740  fprintf(fp, "%c%s", delim, csv->headers[fi]);
1741  }
1742 
1743  if (ferror(fp)) {
1744 
1746  "Could not write to CSV file\n"
1747  " -> %s\n", strerror(errno));
1748 
1750 
1751  return(-1);
1752  }
1753 
1754  return(1);
1755 }
1756 
1757 /**
1758  * Print CSV record data.
1759  *
1760  * If the number of values found on the current line does not match
1761  * the number of header fields, a warning message will be generated.
1762  *
1763  * @param fp pointer to the output stream
1764  * @param csv pointer to the CSVParser structure
1765  *
1766  * @retval 1 if successful
1767  * @retval 0 if no data was found in the CSV table
1768  * @retval -1 if a write error occurred
1769  */
1771 {
1772  char delim = csv->delim;
1773  int ri, fi;
1774 
1775  if (csv->nfields <= 0) {
1776  fprintf(fp, "No fields stored in CSV Table\n");
1777  return(0);
1778  }
1779 
1780  if (csv->nrecs <= 0) {
1781  fprintf(fp, "No records stored in CSV Table\n");
1782  return(0);
1783  }
1784 
1785  for (ri = 0; ri < csv->nrecs; ++ri) {
1786 
1787  fprintf(fp, "%s", csv->values[0][ri]);
1788  for (fi = 1; fi < csv->nfields; ++fi) {
1789  fprintf(fp, "%c%s", delim, csv->values[fi][ri]);
1790  }
1791  fprintf(fp, "\n");
1792  }
1793 
1794  if (ferror(fp)) {
1795 
1797  "Could not write to CSV file\n"
1798  " -> %s\n", strerror(errno));
1799 
1801 
1802  return(-1);
1803  }
1804 
1805  return(1);
1806 }
1807