1 /******************************************************************************
2 * dbf.c
3 ******************************************************************************
4 * Author: Bjoern Berg <clergyman@gmx.de>
5 * dbf Reader and Converter for dBASE files
6 * Version 0.9
7 *
8 ******************************************************************************
9 * $Id: dbf.c,v 1.31 2005/03/16 08:36:16 steinm Exp $
10 *****************************************************************************/
11
12 /** TODO **/
13 /* Currently we do not know how to handle field subrecords in FoxPro and dBASE 4
14 * backlink_exists() works only if no field subrecords are in the table definition (*.dbf)
15 */
16
17 #include <stdlib.h>
18 #include <string.h>
19 #include <libgen.h>
20 #include <assert.h>
21 #include "dbf.h"
22 #include "statistic.h"
23 #include "csv.h"
24 #include "sql.h"
25 #include "odbf.h"
26
27 static struct DB_FSIZE *fsz;
28
29 static int convert = 1;
30 static int keep_deleted = 0;
31 static int dbc = 0; /* 0 = no backlink, 1 = backlink */
32 static int startrecord = 1;
33 static int numrecords = -1;
34 static int quiet = 0;
35
36 unsigned int verbosity = 0;
37 char *tablename = NULL;
38
39
40 /* banner() {{{
41 */
42 static void
banner()43 banner()
44 {
45 fprintf(stderr, PACKAGE_NAME " " VERSION);
46 fprintf(stderr, "\n");
47 fprintf(stderr, _("Copyright 2002-2004 Bjoern Berg"));
48 fprintf(stderr, "\n");
49 }
50 /* }}} */
51
52 /* version() {{{
53 */
54 static void
version()55 version()
56 {
57 fprintf(stderr, VERSION);
58 fprintf(stderr, "\n");
59 }
60 /* }}} */
61
62 /* export_open() {{{
63 * open the export file for writing */
64 FILE *
export_open(const char * file)65 export_open(const char *file)
66 {
67 FILE *result;
68 if (file == NULL || (file[0] == '-' && file[1] == '\0'))
69 return stdout;
70 if ((result = fopen(file, "w")) == NULL) {
71 perror(file);
72 exit(1);
73 }
74 return result;
75 }
76 /* }}} */
77
78 /* export_close() {{{
79 * closes the opened file and stops the write-process */
80 int
export_close(FILE * fp,const char * file)81 export_close(FILE *fp, const char *file)
82 {
83 if (fp == stdout)
84 return 0;
85 if (fclose(fp)) {
86 fprintf(stderr, _("Cannot close output file '%s'."), file);
87 fprintf(stderr, "\n");
88 perror("Error");
89 return 1;
90 }
91 if (verbosity > 2) {
92 fprintf(stderr, _("Output file '%s' was closed successfully."), file);
93 fprintf(stderr, "\n");
94 }
95 return 0;
96 }
97 /* }}} */
98
99 /* dbf_backlink_exists() {{{
100 * checks if a backlink is at the end of the field definition, this backlink is
101 * about 263 byte
102 */
103 #ifdef kkk
dbf_backlink_exists(int fh,const char * file)104 static int dbf_backlink_exists(int fh, const char *file)
105 {
106 int number_of_fields;
107 long pos;
108 char terminator[1], *pt=terminator;
109
110 /* As stated in Microsofts TechNet Article about Visual FoxPro, we have to
111 * proof if the Field Terminator is set, because dbf puts the backlink to
112 * the normal header. The sign 0x0Dh is invalid in the backlink so we can
113 * easily prove for it and verify Microsofts formula.
114 * -- berg, 2003-12-08
115 */
116 pos = rotate4b( lseek(fh, 0L, SEEK_CUR) );
117 lseek(fh, db->header_length-1, SEEK_SET);
118
119 if ((read( fh, (char *)pt, 1)) == -1 ) {
120 perror(file);
121 exit(1);
122 }
123
124 lseek(fh, pos, SEEK_SET);
125
126 number_of_fields = (db->header_length - 296) / 32;
127
128 if ( (number_of_fields != db->records) && *pt != 0x0D) {
129 fprintf(stderr, "Database backlink found!");
130 fprintf(stderr, "\n");
131 return 1;
132 }
133
134 return 0;
135 }
136 #endif
137 /* }}} */
138
139 /* dbf_check {{{
140 * checks if dbf file is valid
141 * File size reported by the operating system must match the logical file size.
142 * Logical file size = ( Length of header + ( Number of records * Length of each record ) )
143 * Exception: Clipper and Visual FoxPro
144 */
145 #ifdef kkkk
146 static int
dbf_check(int fh,const char * file)147 dbf_check(int fh, const char *file)
148 {
149 //u_int32_t filesize, pos, calc_filesize;
150 u_int32_t pos;
151
152 pos = rotate4b( lseek(fh, 0L, SEEK_CUR) );
153 fsz->real_filesize = rotate4b( lseek(fh, 0L, SEEK_END) );
154 lseek(fh, pos, SEEK_SET);
155
156 fsz->calc_filesize = (db->header_length + (db->records * db->record_length) + 1) ;
157
158 if ( fsz->calc_filesize != fsz->real_filesize ) {
159 strcpy(fsz->integrity,"not OK");
160 return 0;
161 }
162
163 strcpy(fsz->integrity,"OK");
164 return 1;
165 }
166 #endif
167 /* }}} */
168
169 /* setStartRecord() {{{
170 * set the first record to read
171 */
172 static int
setStartRecord(FILE * output,P_DBF * p_dbf,const char * filename,const char * startnum)173 setStartRecord(FILE *output, P_DBF *p_dbf,
174 const char *filename, const char *startnum)
175 {
176 startrecord = atoi(startnum);
177 return 0;
178 }
179 /* }}} */
180
181 /* setNumRecords() {{{
182 * set the number of records to read
183 */
184 static int
setNumRecords(FILE * output,P_DBF * p_dbf,const char * filename,const char * recordnum)185 setNumRecords(FILE *output, P_DBF *p_dbf,
186 const char *filename, const char *recordnum)
187 {
188 numrecords = atoi(recordnum);
189 if(numrecords <= 0) {
190 fprintf(stderr, _("Number of records to output must be greater zero."));
191 fprintf(stderr, "\n");
192 }
193 return 0;
194 }
195 /* }}} */
196
197 /* setTablename() {{{
198 * set the name of the table for sql output
199 */
200 static int
setTablename(FILE * output,P_DBF * p_dbf,const char * filename,const char * _tablename)201 setTablename(FILE *output, P_DBF *p_dbf,
202 const char *filename, const char *_tablename)
203 {
204 tablename = strdup(_tablename);
205 return 0;
206 }
207 /* }}} */
208
209 /* setNoConv() {{{
210 * defines if charset converter should be used
211 */
212 static int
setNoConv(FILE * output,P_DBF * p_dbf,const char * filename,const char * level)213 setNoConv(FILE *output, P_DBF *p_dbf,
214 const char *filename, const char *level)
215 {
216 convert = 0;
217 return 0;
218 }
219 /* }}} */
220
221 /* setKeepDel() {{{
222 */
223 static int
setKeepDel(FILE * output,P_DBF * p_dbf,const char * filename,const char * level)224 setKeepDel (FILE *output, P_DBF *p_dbf,
225 const char *filename, const char *level)
226 {
227 keep_deleted = 1;
228 return 0;
229 }
230 /* }}} */
231
232 /* setQuiet() {{{
233 */
234 static int
setQuiet(FILE * output,P_DBF * p_dbf,const char * filename,const char * level)235 setQuiet (FILE *output, P_DBF *p_dbf,
236 const char *filename, const char *level)
237 {
238 quiet = 1;
239 return 0;
240 }
241 /* }}} */
242
243 /* setVerbosity() {{{
244 * sets debug level
245 */
246 static int
setVerbosity(FILE * output,P_DBF * p_dbf,const char * filename,const char * level)247 setVerbosity(FILE *output, P_DBF *p_dbf,
248 const char *filename, const char *level)
249 {
250 if (level[1] != '\0' || level[0] < '0' || level[0] > '9') {
251 fprintf(stderr, _("Invalid debug level ``%s''. Must be from 0 to 9"), level);
252 fprintf(stderr, "\n");
253 return 1;
254 }
255 verbosity = level[0] - '0';
256 return 0;
257 }
258 /* }}} */
259
260 /* writeINFOHdr() {{{
261 */
262 static int
writeINFOHdr(FILE * output,P_DBF * p_dbf,const char * filename,const char * export_filename)263 writeINFOHdr(FILE *output, P_DBF *p_dbf,
264 const char *filename, const char *export_filename)
265 {
266 dbf_file_info(p_dbf);
267 dbf_field_stat(p_dbf);
268 return 0;
269 }
270 /* }}} */
271
272 /* printDBF() {{{
273 * printDBF is the real function that is hidden behind writeLine
274 */
275 static int
printDBF(FILE * output,P_DBF * p_dbf,const unsigned char * record,int header_length,const char * filename,const char * export_filename)276 printDBF(FILE *output, P_DBF *p_dbf,
277 const unsigned char *record, int header_length,
278 const char *filename, const char *export_filename)
279 {
280 int i, columns;
281 const char *value;
282 columns = dbf_NumCols(p_dbf);
283 value = record;
284
285 for (i = 0; i < columns; i++) {
286 char field_type;
287 const char *field_name;
288 int field_length, field_decimals;
289 field_type = dbf_ColumnType(p_dbf, i);
290 field_name = dbf_ColumnName(p_dbf, i);
291 field_length = dbf_ColumnSize(p_dbf, i);
292 field_decimals = dbf_ColumnDecimals(p_dbf, i);
293 printf("%11.11s: %.*s\n", field_name, field_length, value);
294 value += field_length;
295 }
296 return 0;
297 }
298
299 /*}}} */
300
301 /* Options {{{
302 * Added the hyphes to the id so that ids with a single hyphe are also possible.
303 * -- Bjoern Berg, 2003-10-06
304 */
305 struct options {
306 const char *id;
307 headerMethod writeHeader;
308 footerMethod writeFooter;
309 lineMethod writeLine;
310 enum argument {
311 ARG_NONE, /* Method without output file */
312 ARG_OPTION, /* Option with argument */
313 ARG_BOOLEAN, /* Option without argument */
314 ARG_OUTPUT /* Method with output file */
315 } argument;
316 enum class {
317 ARG_CLASS_SET, /* Option to set something */
318 ARG_CLASS_OUTPUT /* Option to set output mode */
319 } class;
320 const char *help, *def_behavior;
321 } options[] = {
322 {
323 "--sql", writeSQLHeader, writeSQLFooter, writeSQLLine, ARG_OUTPUT, ARG_CLASS_OUTPUT,
324 "{filename} -- convert file into sql statements",
325 NULL
326 },
327 {
328 "--trim", setSQLTrim, NULL, NULL, ARG_OPTION, ARG_CLASS_SET,
329 "{r|l|b} -- trim char fields in sql output (right, left, both)",
330 "not to trim"
331 },
332 {
333 "--csv", writeCSVHeader, NULL, writeCSVLine, ARG_OUTPUT, ARG_CLASS_OUTPUT,
334 "{filename} -- convert file into \"comma separated values\"",
335 NULL
336 },
337 {
338 "--dbf", writeDBFHeader, writeDBFFooter, writeDBFLine, ARG_OUTPUT, ARG_CLASS_OUTPUT,
339 "{filename} -- convert file into dBASE file",
340 NULL
341 },
342 {
343 "--separator", setCSVSep, NULL, NULL, ARG_OPTION, ARG_CLASS_SET,
344 "{c} -- set field separator for csv format",
345 "to use ``,''"
346 },
347 {
348 "--tablename", setTablename, NULL, NULL, ARG_OPTION, ARG_CLASS_SET,
349 "{name} -- set name of the table for sql output",
350 "the name of the export file"
351 },
352 {
353 "--start-record", setStartRecord, NULL, NULL, ARG_OPTION, ARG_CLASS_SET,
354 "{number} -- sets first record to read",
355 "1, the first record"
356 },
357 {
358 "--num-records", setNumRecords, NULL, NULL, ARG_OPTION, ARG_CLASS_SET,
359 "{number} -- sets number of records to read",
360 "all"
361 },
362 {
363 "--view-info", writeINFOHdr, NULL, NULL, ARG_NONE, ARG_CLASS_OUTPUT,
364 "write various information and table structure to stdout",
365 NULL
366 },
367 {
368 "--noconv", setNoConv, NULL, NULL, ARG_BOOLEAN, ARG_CLASS_SET,
369 "do not run each record through charset converters",
370 "to use the experimental converters"
371 },
372 {
373 "--nodrop", setNoDrop, NULL, NULL, ARG_BOOLEAN, ARG_CLASS_SET,
374 "disable DROP TABLE statement in sql output",
375 NULL
376 },
377 {
378 "--nocreate", setNoCreate, NULL, NULL, ARG_BOOLEAN, ARG_CLASS_SET,
379 "disable CREATE TABLE statement in sql output",
380 NULL
381 },
382 {
383 "--usecopy", setSQLUsecopy, NULL, NULL, ARG_BOOLEAN, ARG_CLASS_SET,
384 "use COPY instead of INSERT for populating table",
385 NULL
386 },
387 {
388 "--empty-str-is-null", setSQLEmptyStrIsNULL, NULL, NULL, ARG_BOOLEAN, ARG_CLASS_SET,
389 "ouput NULL for empty strings in sql output",
390 NULL
391 },
392 {
393 "--keepdel", setKeepDel, NULL, NULL, ARG_NONE, ARG_CLASS_SET,
394 "output also deleted records",
395 "to skip deleted records"
396 },
397 {
398 "--quiet", setQuiet, NULL, NULL, ARG_NONE, ARG_CLASS_SET,
399 "do not out anything but the record data",
400 "to at least output warnings"
401 },
402 {
403 "--debug", setVerbosity, NULL, NULL, ARG_OPTION, ARG_CLASS_SET,
404 "{0-9} -- set the debug level. 0 is the quietest",
405 "0"
406 },
407 {
408 "--version", NULL, NULL, NULL, ARG_NONE, ARG_CLASS_OUTPUT,
409 "shows the current release number",
410 NULL
411 },
412 { NULL }
413 };
414 /* }}} */
415
416 /* usage() {{{
417 * Displays a well known UNIX style command line options overview
418 */
419 static void
usage(const char * pname)420 usage(const char *pname)
421 {
422 struct options *option;
423 banner();
424 fprintf(stderr, _("Usage: %s [options] dbf-file"), pname);
425 fprintf(stderr, "\n");
426 fprintf(stderr, _("Output the contents of a dBASE table file (.dbf)."));
427 fprintf(stderr, "\n\n");
428 fprintf(stderr, _("Available options:"));
429 fprintf(stderr, "\n");
430
431 for (option = options; option->id; option++) {
432 fprintf(stderr, " %-11s %s\n",
433 option->id, option->help);
434 if (option->def_behavior != NULL)
435 fprintf(stderr, " %-11s (default is %s)\n", "", option->def_behavior);
436 }
437 fprintf(stderr, "\n");
438 fprintf(stderr, _("The options --sql, --csv, --view-info, --version are mutually exclusive."));
439 fprintf(stderr, "\n");
440 fprintf(stderr, _("The last option specified takes precedence."));
441 fprintf(stderr, "\n");
442 fprintf(stderr, _("A single dash (``-'') as a filename specifies stdin or stdout"));
443 fprintf(stderr, "\n");
444 exit(1);
445 }
446 /* }}} */
447
448 /* main() {{{
449 */
450 int
main(int argc,char * argv[])451 main(int argc, char *argv[])
452 {
453 P_DBF *p_dbf;
454 FILE *output = NULL;
455 int record_length, i;
456 const char *filename, *export_filename = NULL;
457 headerMethod writeHeader = NULL;
458 footerMethod writeFooter = NULL;
459 lineMethod writeLine = printDBF;
460 int outputmode = -1; /* Index of option in struct options */
461 unsigned char *record;
462 unsigned int dataset_deleted;
463
464 #ifdef ENABLE_NLS
465 setlocale (LC_ALL, "");
466 setlocale (LC_NUMERIC, "C");
467 bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
468 textdomain (GETTEXT_PACKAGE);
469 #endif
470
471 if (argc < 2) {
472 usage(PACKAGE_NAME); /* Does not return */
473 exit(1);
474 }
475
476 /* Check if someone needs help */
477 for(i=1; i < argc; i++)
478 if(strcmp(argv[i],"-h")==0 || strcmp(argv[i],"--help")==0 || strcmp(argv[i],"/?")==0)
479 usage(PACKAGE_NAME); /* Does not return */
480
481 /* Check if someone wants only version output */
482 for(i=1; i < argc; i++) {
483 if( strcmp(argv[i],"-v")==0 || strcmp(argv[i],"--version")==0 ) {
484 banner();
485 exit(1);
486 }
487 }
488
489 /* fill filename with last argument
490 * Test if last argument is an option or a possible valid filename
491 */
492 filename = argv[--argc];
493 if (filename[0] == '-' && filename[1] != '\0') {
494 fprintf(stderr, _("No input file specified. Please make sure that the last argument is a valid dBASE file."));
495 fprintf(stderr, "\n");
496 exit(1);
497 }
498
499 /* Open the input dBASE file */
500 if(NULL == (p_dbf = dbf_Open(filename))) {
501 fprintf(stderr, _("Could not open dBASE file '%s'."), filename);
502 fprintf(stderr, "\n");
503 exit(1);
504 }
505
506 /* Check for calling name of the programm */
507 if(!strcmp(argv[0], "dbfinfo")) {
508 struct options *option = options;
509 int optionindex;
510 optionindex = 0;
511 while (option->id && strcmp("--view-info", option->id)) {
512 option++;
513 optionindex++;
514 }
515 if (option->id != NULL) {
516 writeHeader = option->writeHeader;
517 writeFooter = option->writeFooter;
518 writeLine = option->writeLine;
519 outputmode = optionindex;
520 } else {
521 fprintf(stderr, _("Could not find predefined option when calling '%s'"), argv[0]);
522 fprintf(stderr, "\n");
523 }
524 }
525
526 /* Scan through arguments looking for options
527 */
528 for(i=1; i < argc; i++) {
529 struct options *option = options;
530 int optionindex;
531 if (argv[i][0] != '-' && argv[i][1] != '-')
532 goto badarg;
533 optionindex = 0;
534 while (option->id && strcmp(argv[i], option->id)) {
535 option++;
536 optionindex++;
537 }
538 if (option->id == NULL) {
539 badarg:
540 fprintf(stderr, _("Unrecognized option ``%s''. Try ``--help'' for a list of options."), argv[i]);
541 fprintf(stderr, "\n");
542 exit(1);
543 }
544 if(option->class == ARG_CLASS_OUTPUT && outputmode >= 0) {
545 fprintf(stderr, _("Output mode cannot be set twice. Has been set with '%s' already. Discarding option '%s'."), options[outputmode].id, option->id);
546 fprintf(stderr, "\n");
547 /* Consume the next parametere containing the output file */
548 if((option->argument == ARG_OUTPUT) && (i < argc))
549 i++;
550 } else {
551 if(option->class == ARG_CLASS_OUTPUT)
552 outputmode = optionindex;
553 switch (option->argument) {
554 case ARG_OUTPUT:
555 if (export_filename) {
556 fprintf(stderr,
557 _("Output file name was already specified as ``%s''. Try the --help for a list of options."), export_filename);
558 fprintf(stderr, "\n");
559 exit(1);
560 }
561 export_filename = argv[++i];
562 /* Fail safe routine to keep sure that the original file can
563 * never be overwritten
564 */
565 if ( strcmp(export_filename, filename) == 0 ) {
566 fprintf(stderr, _("Input file name is equal to output file name. Please choose a different output file name."));
567 fprintf(stderr, "\n");
568 exit(1);
569 }
570 /* FALLTHROUGH */
571 case ARG_NONE:
572 writeHeader = option->writeHeader;
573 writeFooter = option->writeFooter;
574 writeLine = option->writeLine;
575 break;
576 case ARG_OPTION:
577 i++;
578 /* FALLTHROUGH */
579 case ARG_BOOLEAN:
580 /* There can be many -- call them all: */
581 if (option->writeHeader &&
582 option->writeHeader(NULL, p_dbf,
583 filename, argv[i]))
584 exit (1);
585 break;
586 default:
587 assert(!"Unknown type of option argument");
588 }
589 }
590 }
591
592 if (verbosity > 0)
593 banner();
594
595 if(!export_filename || 0 == strcmp(export_filename, "-"))
596 output = stdout;
597 else
598 output = export_open(export_filename);
599
600 /* If the tablename was not set explicitly, use the export file name */
601 if(!tablename && export_filename && 0 != strcmp(export_filename, "-")) {
602 char *ptr;
603 tablename = basename(strdup(export_filename));
604 if(NULL != (ptr = strrchr(tablename, '.'))) {
605 if(ptr != tablename)
606 *ptr = '\0';
607 else {
608 fprintf(stderr, _("Creating the table name from the export file name results in a NULL string."));
609 fprintf(stderr, "\n");
610 exit(1);
611 }
612 }
613 }
614
615
616 if(!tablename && writeHeader == writeSQLHeader) {
617 fprintf(stderr, _("SQL mode requires a tablename to be set, if the output goes to stdout."));
618 fprintf(stderr, "\n");
619 exit(1);
620 } else if (verbosity > 0) {
621 fprintf(stderr, _("Tablename is '%s'"), tablename);
622 fprintf(stderr, "\n");
623 }
624
625 /*
626 * Call the main header-method, which we skipped during the option parsing
627 */
628 if (writeHeader && writeHeader(output, p_dbf, filename, export_filename))
629 exit(1);
630
631 record_length = dbf_RecordLength(p_dbf);
632 if (writeLine) {
633 int endrecord;
634 if ((record = malloc(record_length + 1)) == NULL) {
635 perror("malloc"); exit(1);
636 }
637 record[record_length] = '\0'; /* So the converters know, where to stop */
638
639 if (verbosity > 0) {
640 fprintf(stderr, _("Export from '%s' to '%s'"),filename,
641 output == stdout ? "stdout" : export_filename);
642 fprintf(stderr, "\n");
643 }
644
645 if(0 > (i = dbf_SetRecordOffset(p_dbf, startrecord))) {
646 fprintf(stderr, "Cannot set start offset %d. Error Code %d.", startrecord, i);
647 fprintf(stderr, "\n");
648 exit(1);
649 }
650 if(numrecords < 1)
651 numrecords = dbf_NumRows(p_dbf);
652 endrecord = i + numrecords;
653 if(endrecord > dbf_NumRows(p_dbf)) {
654 endrecord = dbf_NumRows(p_dbf);
655 if(!quiet) {
656 fprintf(stderr, _("Will only output %d rows, because there are no more rows in the dBASE file."), endrecord-i);
657 fprintf(stderr, "\n");
658 }
659 }
660 if(verbosity >= 1) {
661 fprintf(stderr, _("Output records from row %d to %d."), i+1, endrecord);
662 fprintf(stderr, "\n");
663 }
664 while ((0 <= (i = dbf_ReadRecord(p_dbf, record, record_length))) && (i < endrecord))
665 {
666 if(verbosity >= 1) {
667 fprintf(stderr, _("Row %i."), i+1);
668 fprintf(stderr, "\n");
669 }
670 dataset_deleted = 0;
671 if (record[0] == '*' ) {
672 dataset_deleted = 1;
673 }
674
675 /* Look if the dataset is deleted or the end of the dBASE file was
676 * reached without notification by read. The end of each dBASE file is
677 * marked with a dot.
678 */
679 if ( (!dataset_deleted || keep_deleted == 1) && record[0] != 0x1A) {
680 /* automatically convert options */
681 if (convert)
682 cp850andASCIIconvert(record);
683 writeLine(output, p_dbf, record+1, record_length-1,
684 filename, export_filename);
685 } else if ( verbosity >=1 && record[0] != 0x1A) {
686 fprintf(stderr, _("The row %i is set to 'deleted'."), i+1);
687 fprintf(stderr, "\n");
688 }
689 }
690 free(record);
691 }
692 /*
693 * Call the main footer-method, which we skipped during the option parsing
694 */
695 if (writeFooter && writeFooter(output, p_dbf, filename, export_filename))
696 exit(1);
697
698 dbf_Close(p_dbf);
699 export_close(output, export_filename);
700
701 return 0;
702 }
703 /* }}} */
704
705 /*
706 * Local variables:
707 * tab-width: 4
708 * c-basic-offset: 4
709 * End:
710 * vim600: sw=4 ts=4 fdm=marker
711 * vim<600: sw=4 ts=4
712 */
713