There are various solutions to this problem. All of them can be simplified by breaking the problem into several auxiliary functions that will help keep the program logic straight. (note: this is true for any part of the code, but it is especially true for this bit of code).
Data processing to collect information associated with each client can be processed by the struct members contained for each field. (while each field may be narrower in size, your size is 30 fine) For example, by declaring a constant for FLDSZ = 30 , you can create a structure for your information similar to:
typedef struct { char last[FLDSZ]; char first[FLDSZ]; char mi[FLDSZ]; char sal[FLDSZ]; char street[FLDSZ]; char streetno[FLDSZ]; char city[FLDSZ]; char state[FLDSZ]; char zip[FLDSZ]; } sp;
(Ideally, you would like to dynamically allocate some initial number of pointers to a struct, fill in and realloc as needed. For this example, the static number contained in the array is excellent)
To start storing your data, you need a way to split the line into various tokens. strtok perfect here. You just need to read each line, tokenize and save the resulting line as the correct member. With an array of structures, you also need to track a separate index of the structure in addition to coding the way to store individual tokens as the correct member. It is here that the first auxiliary function can make life easier. For example, your complete reading / filling of the data structure can be done using something similar to:
char buf[MAXC] = {0}; sp s[MAXS]; .... while (fgets (buf, MAXC, ifp)) { char *p; size_t idx = 0; for (p = strtok (buf, "|"); p; p = strtok (NULL, "|\n")) { fillsp (&s[n], p, &idx); } if (++n == MAXS) { fprintf (stderr, "MAXS structs filled.\n"); break; } }
(where ifp is the input file stream pointer and n your structure index)
Helper function fillsp is key. It takes a struct sp address, a pointer to the current token, and a pointer to the current idx member index. Based on the idx value, through either the if-then-else line, or better, the switch , you can coordinate the correct member with each token. Something similar to the following fits here:
void fillsp (sp *s, const char *p, size_t *idx) { switch (*idx) { case 0 : strncpy (s->last, p, FLDSZ); if (s->last[FLDSZ - 1] != 0) s->last[FLDSZ - 1] = 0; (*idx)++; break; case 1 : strncpy (s->first, p, FLDSZ); if (s->first[FLDSZ - 1] != 0) s->first[FLDSZ - 1] = 0; (*idx)++; break; case 2 : s->mi[0] = s->mi[1] = 0; *(s->mi) = *p; (*idx)++; break; case 3 : strncpy (s->sal, p, FLDSZ); if (s->sal[FLDSZ - 1] != 0) s->sal[FLDSZ - 1] = 0; (*idx)++; break; case 4 : strncpy (s->streetno, p, FLDSZ); if (s->streetno[FLDSZ - 1] != 0) s->streetno[FLDSZ - 1] = 0; (*idx)++; break; case 5 : strncpy (s->street, p, FLDSZ); if (s->street[FLDSZ - 1] != 0) s->street[FLDSZ - 1] = 0; (*idx)++; break; case 6 : strncpy (s->city, p, FLDSZ); if (s->city[FLDSZ - 1] != 0) s->city[FLDSZ - 1] = 0; (*idx)++; break; case 7 : strncpy (s->state, p, FLDSZ); if (s->state[FLDSZ - 1] != 0) s->state[FLDSZ - 1] = 0; (*idx)++; break; case 8 : strncpy (s->zip, p, FLDSZ); if (s->zip[FLDSZ - 1] != 0) s->zip[FLDSZ - 1] = 0; (*idx)++; break; default : fprintf (stderr, "error: index outside allowed 0-8.\n"); exit (EXIT_FAILURE); } }
(note: idx updated when the correct case is found. The default case warns of an invalid index, but you should also add strlen checks in addition to forced nul termination).
After saving your data, the next task is to simply read the template file and replace the '$X' format placeholder with the appropriate field from your structure. One thing you need to know about is that after each reading you will need to rewind the file-pointer template ( 'tfp' ) so that it is used again for the next client. The auxiliary function helps here again. The logic for displaying welcome information for each client can be simple:
for (i = 0; i < n; i++) /* show welcome for each */ welcome (&s[i], tfp);
(note: in fact, you would look at the clientโs name and then just pass that address to the welcome function, but in this example each will be printed)
When parsing each line of a template in the welcome function, strchr provides a simple way to both check and find any '$' in any given line. Using a pair of character pointers, you can easily scroll through each line and find / replace each format specifier. To create the actual line for output, you can use strcat and strncat . For example, for welcome you can use something similar to the following:
void welcome (sp *s, FILE *tfp) { char buf[MAXC] = {0}; while (fgets (buf, MAXC, tfp)) { char *p = buf, *ep; char obuf[MAXC] = {0}; while (*p && (ep = strchr (p, '$'))) { strncat (obuf, p, ep++ - p); strcat (obuf, rtnmemb (s, *ep++ - '0')); p = ep; } strcat (obuf, p); printf ("%s", obuf); } putchar ('\n'); rewind (tfp); }
The last helper function used by welcome , rtnmemb used to return the element indicated by the character following '$' . (note: since you are reading the number as a character, you need to subtract '0' from the ASCII value to hide it to a numerical value.) The rtnmemb implementation may look like this:
char *rtnmemb (sp *s, size_t idx) { switch (idx) { case 0 : return s->last; break; case 1 : return s->first; break; case 2 : return s->mi; break; case 3 : return s->sal; break; case 4 : return s->streetno; break; case 5 : return s->street; break; case 6 : return s->city; break; case 7 : return s->state; break; case 8 : return s->zip; break; default : printf ("error: requested member outside allowed 0-8.\n"); } return NULL; }
Combining all the pieces of the puzzle, you can do something like:
#include <stdio.h> #include <stdlib.h> #include <string.h> /* constants (file size, max struct, max char) */ enum { FLDSZ = 30, MAXS = 64, MAXC = 128 }; typedef struct { char last[FLDSZ]; char first[FLDSZ]; char mi[FLDSZ]; char sal[FLDSZ]; char street[FLDSZ]; char streetno[FLDSZ]; char city[FLDSZ]; char state[FLDSZ]; char zip[FLDSZ]; } sp; void fillsp (sp *s, const char *p, size_t *idx); char *rtnmemb (sp *s, size_t idx); void welcome (sp *s, FILE *tfp); int main (int argc, char **argv) { char buf[MAXC] = {0}; sp s[MAXS]; size_t i, n = 0; /* input, template, output streams */ FILE *ifp = argc > 1 ? fopen (argv[1], "r") : stdin; FILE *tfp = fopen (argc > 2 ? argv[2] : "../dat/template.txt", "r"); FILE *ofp = argc > 3 ? fopen (argv[3], "w") : stdout; if (!ifp || !tfp || !ofp) { /* validate streams open */ fprintf (stderr, "error: file open failed.\n"); return 1; } while (fgets (buf, MAXC, ifp)) { /* read each line of data */ char *p; size_t idx = 0; /* tokenize/save in struct 's[n]' */ for (p = strtok (buf, "|"); p; p = strtok (NULL, "|\n")) { fillsp (&s[n], p, &idx); } if (++n == MAXS) { /* limit reached */ fprintf (stderr, "MAXS structs filled.\n"); break; } } for (i = 0; i < n; i++) /* show welcome for each */ welcome (&s[i], tfp); if (ifp != stdin) fclose (ifp); /* close files */ if (ofp != stdout) fclose (ofp); fclose (tfp); return 0; } /* store 'p' in correct stuct 's' member based on index 'idx' */ void fillsp (sp *s, const char *p, size_t *idx) { switch (*idx) { case 0 : strncpy (s->last, p, FLDSZ); if (s->last[FLDSZ - 1] != 0) s->last[FLDSZ - 1] = 0; (*idx)++; break; case 1 : strncpy (s->first, p, FLDSZ); if (s->first[FLDSZ - 1] != 0) s->first[FLDSZ - 1] = 0; (*idx)++; break; case 2 : s->mi[0] = s->mi[1] = 0; *(s->mi) = *p; (*idx)++; break; case 3 : strncpy (s->sal, p, FLDSZ); if (s->sal[FLDSZ - 1] != 0) s->sal[FLDSZ - 1] = 0; (*idx)++; break; case 4 : strncpy (s->streetno, p, FLDSZ); if (s->streetno[FLDSZ - 1] != 0) s->streetno[FLDSZ - 1] = 0; (*idx)++; break; case 5 : strncpy (s->street, p, FLDSZ); if (s->street[FLDSZ - 1] != 0) s->street[FLDSZ - 1] = 0; (*idx)++; break; case 6 : strncpy (s->city, p, FLDSZ); if (s->city[FLDSZ - 1] != 0) s->city[FLDSZ - 1] = 0; (*idx)++; break; case 7 : strncpy (s->state, p, FLDSZ); if (s->state[FLDSZ - 1] != 0) s->state[FLDSZ - 1] = 0; (*idx)++; break; case 8 : strncpy (s->zip, p, FLDSZ); if (s->zip[FLDSZ - 1] != 0) s->zip[FLDSZ - 1] = 0; (*idx)++; break; default : fprintf (stderr, "error: index outside allowed 0-8.\n"); exit (EXIT_FAILURE); } } /* return correct member of struct 's' based on 'idx' */ char *rtnmemb (sp *s, size_t idx) { switch (idx) { case 0 : return s->last; break; case 1 : return s->first; break; case 2 : return s->mi; break; case 3 : return s->sal; break; case 4 : return s->streetno; break; case 5 : return s->street; break; case 6 : return s->city; break; case 7 : return s->state; break; case 8 : return s->zip; break; default : printf ("error: requested member outside allowed 0-8.\n"); } return NULL; } void welcome (sp *s, FILE *tfp) { char buf[MAXC] = {0}; while (fgets (buf, MAXC, tfp)) { char *p = buf, *ep; char obuf[MAXC] = {0}; while (*p && (ep = strchr (p, '$'))) { strncat (obuf, p, ep++ - p); strcat (obuf, rtnmemb (s, *ep++ - '0')); p = ep; } strcat (obuf, p); printf ("%s", obuf); } putchar ('\n'); rewind (tfp); }
Output
$ ./bin/str_template ../dat/data.txt Welcome back, Jane! We hope that you and all the members of the Public family are constantly reminding your neighbours there on Maple Street to shop with us. As usual, we will ship your order to Ms. Jane Q. Public 600 Maple Street Your Town, Iowa 12345 Welcome back, Fred! We hope that you and all the members of the Penner family are constantly reminding your neighbours there on that Street to shop with us. As usual, we will ship your order to Mr. Fred R. Penner 123 that Street Winnipeg, MB R3T 2N2 Welcome back, Mark! We hope that you and all the members of the Gardner family are constantly reminding your neighbours there on The Avenue to shop with us. As usual, we will ship your order to Mr. Mark E. Gardner 76 The Avenue Toronto, ON M5W 1E6 Welcome back, Hilda! We hope that you and all the members of the Acton family are constantly reminding your neighbours there on What Blvd to shop with us. As usual, we will ship your order to Mrs. Hilda P. Acton 66 What Blvd Winnipeg, MB R3T 2N2
Look at the approach to the problem. It is often useful to replace a long chain of if-then-else statements with a switch . Both are acceptable, and this applies to any of the different approaches to your problem. While any approach correctly processes the data, it is readable and quite effective and provides the correct conclusion, in the end it is a matter of taste. Let me know if you have any questions regarding the approach.