/* Program to perform file merge of product codes and names. Input: Two files, "lst10-6a.dat" and "lst10-6b.dat", containing codes and names. Codes are in range 0 - 999,999, names are from 1 to thirty characters. Input files must be in ascending order. No duplicates. Incorrect entries are echoed to the screen and to the file "lst10-6.err". Output: A file, "lst10-6c.dat" containing merged data in ascending order. If both input files contain the same codes and different names, the name selected by the user is written. */ #include #include /* For string comparisons */ #include #define NAMELENG (30) #define CODELENG (7) FILE * file_open(char name[], char access_mode[]); /* See listing 10-2b */ int load_record(FILE *in_file, char code[], char name_buffer[]); /* reads name & code from file; returns 1 (success), or 0 (failure). */ void write_user_choice ( /* Asks user which code & name to write; writes choice to file. */ char code[], /* The code for which two different names occur */ char nameA[], /* 1st name choice for user */ char nameB[], /* 2nd name choice for user */ FILE *outfile /* file to write to */ ); main() { FILE *inA, *inB, *outC; char codeA[CODELENG], codeB[CODELENG]; char nameA[NAMELENG+1], nameB[NAMELENG+1]; int A_nonempty, B_nonempty; /* == 1 iff data loaded from file */ int ordering; /* <0 if record from file A should be output next, ==0 if codes equal, >0 if record from file B should be output next. */ /* Open files. */ inA = file_open("lst10-6a.dat", "r"); inB = file_open("lst10-6b.dat", "r"); outC = file_open("lst10-6c.dat", "w"); /* Load record from file A. */ A_nonempty = load_record(inA, codeA, nameA); /* Load record from file B. */ B_nonempty = load_record(inB, codeB, nameB); while (A_nonempty || B_nonempty) { /* have more data? */ /* First set the ordering: A goes first if B was empty or if A comes before B; vice versa for B. */ ordering = B_nonempty - A_nonempty; /* -1 (B empty), 1 (A empty) 0 (neither empty). */ if (ordering == 0) { /* both files nonempty? */ ordering = strcmp(codeA, codeB); /* choose correct ordering */ } if (ordering < 0) { /* A goes first */ fprintf(outC, "%6s %s\n", codeA, nameA); /*write record A*/ A_nonempty = load_record(inA, codeA, nameA); /*reload from A*/ } else if (ordering > 0) { /* B goes first */ fprintf(outC, "%6s %s\n", codeB, nameB); /*write record B*/ B_nonempty = load_record(inB, codeB, nameB); /*reload from B*/ } else { /* (record codes equal) */ if (strcmp(nameA, nameB) == 0) { /* record names are equal */ /* write either record */ fprintf(outC, "%6s %s\n", codeB, nameB); } else { /* (names are different) */ /* Ask user which name to write, and write the code and the name the user selected to file C. */ write_user_choice(codeA, nameA, nameB, outC); } /* Load new records from both files A and B. */ A_nonempty = load_record(inA, codeA, nameA); /*reload from A*/ B_nonempty = load_record(inB, codeB, nameB); /*reload from B*/ } } /* end of while */ /* Close files. */ fclose(outC); fclose(inA); fclose(inB); return 0; } #include /* for tolower() */ /******** load_record *********** On Entry: Parameter in_file refers to a correct open input file. On Exit: A code and name will have been read from the input file. The name is stored in the parameter name_buffer, and the code in parameter code. If a name is too long, it will be truncated and a warning printed to the screen and to "lst10-6.err". Returns 1 for success, 0 for failure. */ void warning(FILE *, int inchar, char code[], char name[]); void echo_line(FILE *infile, FILE *outfile); int load_record(FILE *in_file, char code[], char name_buffer[]) { int status, length, inchar; FILE * err_file; status = fscanf(in_file, "%6s ", code); /* Try reading the code. */ if (status == EOF) { return 0; /* (function terminates). */ } /* (Here code was input correctly.) */ /* Read a string into name_buffer. No need to check EOF (will be caught next time when reading the code). */ fgets(name_buffer, NAMELENG+1, in_file); length = strlen(name_buffer) - 1; /* Get position of last char */ /* If last character in name_buffer is newline (entire line read OK): */ if (name_buffer[length] == '\n') { name_buffer[length] = '\0'; /* Delete it from name_buffer. */ return 1; } /* If the next character in the input is newline (again all OK): */ inchar = getc(in_file); if (inchar == '\n') { return 1; } /* (name is too long) - Print warning to screen and error file. */ err_file = file_open("lst10-6.err", "a"); warning(stderr, inchar, code, name_buffer); /* warning to screen */ warning(err_file, inchar, code, name_buffer); /* warning to err_file */ /* Copy the rest of the input line to the screen and error file. */ echo_line(in_file, err_file); fclose(err_file); return 1; } /************ warning ************** On Entry: f is an open output file. inchar is the first data char to write after the warning, code is the code, name is the part name. On Exit: Warning for name too long is written to f. */ void warning(FILE *f, int inchar, char code[], char name[]) { fprintf ( f, "WARNING: Code %s, Name too long:\n %s%c", code, name, inchar ); } /************ echo_line ************** On Entry: infile open for input, outfile open for output. On Exit: Rest of current line copied from infile to outfile & to screen */ void echo_line(FILE *infile, FILE *outfile) { int ch; /* Must copy at least 1 char (the return) therefore do...while. */ do { ch = getc(infile); if (ch==EOF) { /* Shouldn't happen, but check anyway. */ return; } putc(ch, outfile); putc(ch, stderr); } while (ch != '\n'); } /*********** write_user_choice ************ On Entry: outfile is the open output file. On Exit: The user will have been warned that two different names have the same code, and the code, with the name of the user's choice, will be written to outfile. */ void write_user_choice ( /* Asks user which code & name to write; writes choice to file. */ char code[], /* The code for which two different names occur */ char nameA[], /* 1st name choice for user */ char nameB[], /* 2nd name choice for user */ FILE *outfile /* file to write to */ ) { char choice; fprintf(stderr, "ERROR: Code %s has two different names,\n", code); fprintf(stderr, "A: \"%s\" and B: \"%s\"\n", nameA, nameB); do { fprintf(stderr, "Please select A or B: "); scanf(" %c", &choice); choice = tolower(choice); /* (See tolower in Appendix D.) */ getchar(); /* Throw away the return after the user's choice */ switch (choice) { case 'a': fprintf(outfile, "%6s %s\n", code, nameA); break; case 'b': fprintf(outfile, "%6s %s\n", code, nameB); break; default: fprintf(stderr, "You must answer A or B.\n"); } } while (choice!='a' && choice !='b'); } FILE * file_open (char name[], char access_mode[]) { FILE * f; f = fopen (name, access_mode); if (f == NULL) { /* error? */ /* Library function perror prints an informative message. */ perror ("Cannot open file"); exit (1); /* Terminate after printing error message. */ } return f; /* Only happens if fopen succeeded. */ }