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