/* * mdbconv for UNIX and commandlines */ #define VERSION "1.6" /* * History * * Written by Peter Kerney (peterk@sydney.sgi.com) Thu Oct 1 13:08:39 AEST 1998 * * Uses ANSI C (although strdup() is non-ANSI) * Only tested on a 32bit big-endian (non-intel) machine * Be warned that sizeof is used extensively and if the bit * sizes change then this could adversely affect the output * * This program has only been tested with UNIX text files (non-dos) * and both comma and tab delimited files. Double quotes may be used * around fields but I am sure that the code will fail if you have * a double quote inside the field eg. "field ""1"" here" (sorry) * */ /* * 1.1 Fixed the calculation of record ID's in setUniqueID() * as the shift values were incorrect * from 24 & 16 to 16 & 8, databases with >511 records would fail * * Updated by Peter Kerney (peterk@sydney.sgi.com) * */ /* * 1.2 Made safe for little-endian machines (Intel). * Corrected minor spelling errors. * * Updated by Dennis S. Hennen Fri Dec 18 18:04:51 EST 1998 * */ /* * 1.3 by "Christopher B. Moore" * I've got a csv data file that was generated by an HP200 database * converter. It puts all fields in quotes and denotes blank fields with * "". This breaks the converter (core dump) but it is easy to fix. I * suggest that line 284 be changed from * if (p[0]=='"') { quote=1; p++;end=strchr(&p[1],'"'); } * to * if (p[0]=='"') { quote=1; ++p; end=strchr(&p[0],'"'); } * */ /* * 1.4 wally grotophorst - wallyg@timesync.gmu.edu * The only change I made to the source code was to * correct the spelling of delimiter... * */ /* * 1.5 Peter Kerney (peterk@sydney.sgi.com) * Included printing of version string * Merged all the updates so far * Renamed to mdbconv.c (just for the 8.3 filename people) * and ones like me that don't like typing. * Forwarded to all above */ /* * 1.6 Keith Thompson (kst@cts.com) * * Replaced non-standard strdup() with my own (trivial) dupstr(). * * Main returns int, not void. * * Added blanks to avoid spurious trigraph in comment (gets warning * from gcc). * * Added exit(EXIT_SUCCESS) at end. * * Channeled error handling through new die_horribly() function. * * Replaced non-portable multi-character character constant with * call to new string_to_DWord function. * * Lots of reformatting (previous authors seem to have been allergic * to whitespace). * * Added typedefs for specific sizes of signed and unsigned integers, * for easier portability. If these are incorrect for some * system, you'll get an immediate run-time assertion error. * * Miscellaneous stylistic improvements (IMHO). * * Deleted "#include " (not used). * * I've confirmed that, for at least one database, the generated pdb * file is identical to the one generated by the previous version * (i.e., any errors are probably subtle ones). */ #include #include #include #include #include extern char *optarg; extern int optind; extern int optopt; extern int opterr; extern int optreset; /* * For target machines with different sizes for predefined types, * just change the following typedefs. * (These could be defined more portably using , but the * resulting nest of ifdefs would be too confusing.) * (For C9X, you could use the exact-width types in .) */ typedef signed char signed_8; typedef unsigned char unsigned_8; typedef signed short signed_16; typedef unsigned short unsigned_16; typedef signed long signed_32; typedef unsigned long unsigned_32; /* * stolen from the Pilot include files */ typedef unsigned_8 Boolean; typedef unsigned_8 Byte; typedef unsigned_16 UInt; typedef unsigned_16 Word; typedef unsigned_32 DWord; typedef DWord LocalID; /* local (card relative) chunk ID */ #define dlkMaxUserNameLength 40 #define dlkUserNameBufSize (dlkMaxUserNameLength + 1) #define dmRecNumCategories 16 /* number of categories */ #define dmCategoryLength 16 /* 15 chars + 1 null terminator */ #define dmDBNameLength 32 /* 31 chars + 1 null terminator */ #define dmHdrAttrResDB 0x0001 /* Resource database */ #define dmHdrAttrReadOnly 0x0002 /* Read Only database */ #define dmHdrAttrAppInfoDirty 0x0004 /* Set if Application Info */ /* block is dirty */ #define dmHdrAttrBackup 0x0008 /* Set if database should */ /* be backed up to PC */ /* * supplied by mobilegeneration */ typedef struct { char currentDatabaseName[32]; Boolean useHardKeys; Boolean confirmDatabaseDelete; Boolean confirmRecordDelete; Boolean hold1; Boolean hold2; Boolean hold3; Boolean hold4; Boolean hold5; char registrationCode[dlkUserNameBufSize]; char hold[216 - dlkUserNameBufSize]; } MobileDBPreferenceType; typedef struct { UInt renamedCategories; char categoryLabels[dmRecNumCategories][dmCategoryLength]; Byte categoryUniqIDs[dmRecNumCategories]; Byte lastUniqID; Byte reserved1; Word reserved2; } MobileAppInfoType; typedef MobileAppInfoType *MobileAppInfoPtr; /* * stolen from the Pilot include files */ typedef struct { LocalID localChunkID; /* local chunkID of a record */ Byte attributes; /* record attributes; */ Byte uniqueID[3]; /* unique ID of record; should */ /* not be 0 for a legal record. */ } RecordEntryType; typedef RecordEntryType *RecordEntryPtr; typedef struct { LocalID nextRecordListID; /* local chunkID of next list */ Word numRecords; /* number of records in this list */ Word firstEntry; /* array of Record/Rsrc entries */ /* starts here */ } RecordListType; typedef RecordListType *RecordListPtr; typedef struct { Byte name[dmDBNameLength]; /* name of database */ Word attributes; /* database attributes */ Word version; /* version of database */ DWord creationDate; /* creation date of database */ DWord modificationDate; /* latest modification date */ DWord lastBackupDate; /* latest backup date */ DWord modificationNumber; /* modification number of database */ LocalID appInfoID; /* application specific info */ LocalID sortInfoID; /* app specific sorting info */ DWord type; /* database type */ DWord creator; /* database creator */ DWord uniqueIDSeed; /* used to generate unique IDs. */ /* Note that only the low order */ /* 3 bytes of this is used (in */ /* RecordEntryType.uniqueID). */ /* We are keeping 4 bytes for */ /* alignment purposes. */ RecordListType recordList; /* first record list */ } DatabaseHdrType; typedef DatabaseHdrType *DatabaseHdrPtr; typedef DatabaseHdrPtr *DatabaseHdrHand; char *dupstr(char *str) { char *result = malloc(strlen(str) + 1); strcpy(result, str); return result; } /* dupstr */ DWord string_to_DWord(char *str) { /* * This function replaces the use of multi-character character * constants. Specifically, the character constant 'Mdb1' is replaced * by the call string_to_DWord("Mdb1"). * * I'm not certain the ordering I use here is the intended one * (it matches gcc on Solaris). */ char *ptr; DWord result = 0; for (ptr = str; *ptr != '\0'; ptr ++) { result = result * 256 + *ptr; } return result; } /* string_to_DWord */ /* * Stolen from makedoc7a */ Word SwapWord21(Word r) { return (r>>8) + (r<<8); } /* SwapWord21 */ Word SwapWord12(Word r) { return r; } /* SwapWord12 */ DWord SwapLong4321(DWord r) { return ((r>>24) & 0xFF) + (r<<24) + ((r>>8) & 0xFF00) + ((r<<8) & 0xFF0000); } /* SwapLong4321 */ DWord SwapLong1234(DWord r) { return r; } /* SwapLong1234 */ Word (*SwapWord)(Word r) = NULL; DWord (*SwapLong)(DWord r) = NULL; /* * copy bytes into a word and double word and see how they fall, * then choose the appropriate swappers to make things come out * in the right order. */ int SwapChoose(void) { union { char b[2]; Word w; } w; union { char b[4]; DWord d; } d; strncpy(w.b, "\1\2", 2); strncpy(d.b, "\1\2\3\4", 4); if (w.w == 0x0201) { SwapWord = SwapWord21; } else if (w.w == 0x0102) { SwapWord = SwapWord12; } else { return 0; } if (d.d == 0x04030201) { SwapLong = SwapLong4321; } else if (d.d == 0x01020304) { SwapLong = SwapLong1234; } else { return 0; } return 1; } /* SwapChoose */ /* * Fixes for little-endian machines -- added by dsh * */ void WriteDatabaseHdrType(DatabaseHdrType hdr, FILE* out) { hdr.attributes = SwapWord(hdr.attributes); hdr.version = SwapWord(hdr.version); hdr.creationDate = SwapLong(hdr.creationDate); hdr.modificationDate = SwapLong(hdr.modificationDate); hdr.lastBackupDate = SwapLong(hdr.lastBackupDate); hdr.modificationNumber = SwapLong(hdr.modificationNumber); hdr.appInfoID = SwapLong(hdr.appInfoID); hdr.sortInfoID = SwapLong(hdr.sortInfoID); hdr.type = SwapLong(hdr.type); hdr.creator = SwapLong(hdr.creator); hdr.uniqueIDSeed = SwapLong(hdr.uniqueIDSeed); hdr.recordList.nextRecordListID = SwapLong(hdr.recordList.nextRecordListID); hdr.recordList.numRecords = SwapWord(hdr.recordList.numRecords); fwrite(&hdr, sizeof(hdr) - 2, 1, out); } /* WriteDatabaseHdrType */ void WriteRecordEntryType( RecordEntryType *recordList, int numRecords, FILE *out) { RecordEntryType re; int i; for (i = 0; i < numRecords + 4; i ++) { re = recordList[i]; re.localChunkID = SwapLong(re.localChunkID); fwrite(&re, sizeof(RecordEntryType), 1, out); } } /* WriteRecordEntryType */ void WriteMobileAppInfoType(MobileAppInfoType appInfo, FILE* out) { appInfo.renamedCategories = SwapWord(appInfo.renamedCategories); appInfo.reserved2 = SwapWord(appInfo.reserved2); fwrite(&appInfo, sizeof(appInfo), 1, out); } /* WriteMobileAppInfoType */ void WriteWord(Word word, FILE* out) { Word wrd; wrd = SwapWord(word); fwrite(&wrd, sizeof(wrd), 1, out); } /* WriteWord */ /* end of dsh additions */ #define MAX_FIELD_LENGTH 200 #define NUM_FIELDS 20 char *infile = NULL; char *outfile = NULL; char *databaseName = NULL; char delim = '\t'; /* default delimiter is a tab */ Boolean verbose = 0; FILE *in = NULL; FILE *out = NULL; int numRecords = 0; char line[4096]; char *fieldNames[NUM_FIELDS]; char *fieldWidths[NUM_FIELDS]; int numFields = 0; char **data; Word record; Word field; Boolean quote; DatabaseHdrType hdr; RecordEntryType *recordList; MobileAppInfoType appInfo; void setUniqueID(int r, int v) { recordList[r].uniqueID[0]=(v&0xff0000)>>16; recordList[r].uniqueID[1]=(v&0xff00)>>8; recordList[r].uniqueID[2]=(v&0xff); } /* setUniqueIDsetUniqueID */ void writeRecordHeader(void) { Byte h[6]={0xff, 0xff, 0xff, 0x01, 0xff, 0x00}; fwrite(h, 1, 6, out); } /* writeRecordHeader */ void writeRecordEnd(void) { Byte e[2]={0x00, 0xff}; fwrite(e, 1, 2, out); } /* writeRecordEnd */ void parseWidths(char *str) { char *s = str; int i = 0; while (1) { fieldWidths[i] = s; s = strchr(s, ','); if (s == NULL) { break; } *s = '\0'; s++; i++; } } /* parseWidths */ void die_horribly(void) { if (out != NULL) { fclose(out); } if (in != NULL) { fclose(in); } /* * If the following line doesn't compile, you can change it to * exit(1) (though any ANSI compiler should declare EXIT_FAILURE * in ). */ exit(EXIT_FAILURE); } /* die_horribly */ int main(int argc, char *argv[]) { int c, i; /* * Make sure all the types are the right sizes. */ assert(CHAR_BIT == 8); assert(CHAR_BIT * sizeof(signed_8) == 8); assert(CHAR_BIT * sizeof(unsigned_8) == 8); assert(CHAR_BIT * sizeof(signed_16) == 16); assert(CHAR_BIT * sizeof(unsigned_16) == 16); assert(CHAR_BIT * sizeof(signed_32) == 32); assert(CHAR_BIT * sizeof(unsigned_32) == 32); for (i = 0 ; i < NUM_FIELDS ; i++) { fieldWidths[i] = "80"; } while ((c=getopt(argc, argv, "i:o:n:w:d:v")) != -1) { switch (c) { case 'i': infile = optarg; break; case 'o': outfile = optarg; break; case 'n': databaseName = optarg; break; case 'w': parseWidths(optarg); break; case 'd': delim = optarg[0]; break; case 'v': verbose = 1; break; } } if (infile == NULL || outfile == NULL || databaseName == NULL) { fprintf(stderr, "%s (Version %s)\n", argv[0], VERSION); fprintf(stderr, "usage: %s -i infile -o outfile -n databasename " "-w widths -d delimiter -v\n", argv[0]); die_horribly(); } /* stolen from Makedoc7a */ if ( ! SwapChoose()) { fprintf(stderr, "\nfailed to select proper byte swapping algorithm\n"); die_horribly(); } if ((in = fopen(infile, "r")) == NULL) { perror(infile); die_horribly(); } while (fgets(line, 4096, in) != NULL) { line[strlen(line)-1] = '\0'; /* get rid of the new-line character */ if (numRecords==0) { /* get the field names */ char *p = line; char *end; Boolean finished = 0; numFields = 0; for (i=0 ; i%s<", data[record * numFields + field]); } printf("-\n"); } } /* * we now have all the data required */ if ((out = fopen(outfile, "w")) == NULL) { perror(outfile); die_horribly(); } /* * database header */ for (i = 0; i < dmDBNameLength; i++) { int len = strlen(databaseName); if (i < len) { hdr.name[i] = databaseName[i]; } else { hdr.name[i] = '\0'; } } hdr.attributes = 0x8; hdr.version = 0x0; hdr.creationDate = 892679477; /* Wed 1998-04-15 22:31:17 GMT (?) */ hdr.modificationDate = 892679477; hdr.lastBackupDate = 892679477; hdr.modificationNumber = 0; hdr.appInfoID = 0x0; hdr.sortInfoID = 0x0; /* * hdr.type = 'Mdb1'; * hdr.creator = 'Mdb1'; * * Note: Different compilers (e.g., Solaris cc and gcc) handle * this differently. See comments in string_to_DWord. */ hdr.type = hdr.creator = string_to_DWord("Mdb1"); hdr.uniqueIDSeed = numRecords+4; hdr.recordList.nextRecordListID = 0x0; hdr.recordList.numRecords = numRecords+4; WriteDatabaseHdrType(hdr, out); /* we will need to come back and redo this */ /* after calculating the appinfo location */ /* * record list */ recordList = malloc(sizeof(RecordEntryType) * (numRecords + 4)); WriteRecordEntryType(recordList, numRecords, out); /* we will need to come back and redo this */ /* after calculating the record locations */ /* * 2 bytes of filler */ WriteWord(0, out); /* * appinfo */ hdr.appInfoID = ftell(out); appInfo.renamedCategories = 0x0; strncpy(appInfo.categoryLabels[0], "Unfiled ", dmCategoryLength); strncpy(appInfo.categoryLabels[1], "FieldLabels ", dmCategoryLength); strncpy(appInfo.categoryLabels[2], "DataRecords ", dmCategoryLength); strncpy(appInfo.categoryLabels[3], "DataRecordsFout ", dmCategoryLength); strncpy(appInfo.categoryLabels[4], "Preferences ", dmCategoryLength); strncpy(appInfo.categoryLabels[5], "DataType ", dmCategoryLength); strncpy(appInfo.categoryLabels[6], "FieldLengths ", dmCategoryLength); for (i = 7; i <= 15; i ++) { memset(appInfo.categoryLabels[i], '\0', dmCategoryLength); } for (i = 0; i < dmRecNumCategories; i++) { appInfo.categoryUniqIDs[i] = i; } appInfo.lastUniqID = dmRecNumCategories - 1; appInfo.reserved1 = 0x0; appInfo.reserved2 = 0x0; WriteMobileAppInfoType(appInfo, out); /* * actual records */ /* * 1 field names */ recordList[0].localChunkID = ftell(out); recordList[0].attributes = 0x1; setUniqueID(0, 1); writeRecordHeader(); for (field = 0; field < numFields; field++) { WriteWord(field, out); fwrite(fieldNames[field], strlen(fieldNames[field]), 1, out); } writeRecordEnd(); /* * 4 prefs (20x01) */ recordList[1].localChunkID = ftell(out); recordList[1].attributes = 0x4; setUniqueID(1, 2); writeRecordHeader(); for (field = 0; field < NUM_FIELDS; field++) { Byte b; WriteWord(field, out); b = 1; fwrite(&b, sizeof b, 1, out); } writeRecordEnd(); /* * 5 field types (20x"str") */ recordList[2].localChunkID = ftell(out); recordList[2].attributes = 0x5; setUniqueID(2, 3); writeRecordHeader(); for (field = 0; field < NUM_FIELDS; field++) { char str[4] = "str"; WriteWord(field, out); fwrite(str, 3, 1, out); } writeRecordEnd(); /* * 6 field widths (20xstring) */ recordList[3].localChunkID = ftell(out); recordList[3].attributes = 0x6; setUniqueID(3, 4); writeRecordHeader(); for (field = 0; field < NUM_FIELDS; field++) { WriteWord(field, out); fwrite(fieldWidths[field], strlen(fieldWidths[field]), 1, out); } writeRecordEnd(); /* * 2 data records */ for (record = 0; record < numRecords; record++) { recordList[record+4].localChunkID = ftell(out); recordList[record+4].attributes = 0x2; setUniqueID(record+4, record+4+1); writeRecordHeader(); for (field = 0; field < numFields; field++) { WriteWord(field, out); fwrite(data[record * numFields + field], strlen(data[record * numFields + field]), 1, out); } writeRecordEnd(); } /* * now go back and rewrite the header & recordList */ rewind(out); WriteDatabaseHdrType(hdr, out); WriteRecordEntryType(recordList, numRecords, out); fclose(out); fclose(in); /* * If the following line doesn't compile, you can change it to * exit(0) (though any ANSI compiler should declare EXIT_SUCCESS * in ). */ exit(EXIT_SUCCESS); } /* main */