User inserts data in a format:\" [NAME], [SURNAME], [INDEX] \". Errors codes:
0 -- everything is loaded to the structure properly
1 -- not loaded to structure pr
Consider using return value from sscanf:
#include <stdio.h>
#include <assert.h>
typedef struct student_t
{
char name[20];
char surname[40];
int index;
} student_t;
enum parse_error_t {
E_OK,
E_NOT_LOADED_PROPERLY,
E_ONLY_NAME_LOADED,
E_NAME_SURNAME_LOADED
};
enum parse_error_t parse_input(char *buf, student_t *out) {
int matches_count;
if (buf == NULL) {
return E_NOT_LOADED_PROPERLY;
}
matches_count = sscanf(buf, "%20s %[^, 40]%*[, ]%d", out->name, out->surname, &out->index);
switch(matches_count) {
case 0:
return E_NOT_LOADED_PROPERLY;
case 1:
return E_ONLY_NAME_LOADED;
case 2:
return E_NAME_SURNAME_LOADED;
case 3:
return E_OK;
default:
return E_NOT_LOADED_PROPERLY;
}
}
int main() {
char *in1 = NULL;
char *in2 = "John";
char *in3 = "John, Deep";
char *in4 = "John, Deep, 999";
student_t student;
assert(parse_input(in1, &student) == E_NOT_LOADED_PROPERLY);
assert(parse_input(in2, &student) == E_ONLY_NAME_LOADED);
assert(parse_input(in3, &student) == E_NAME_SURNAME_LOADED);
assert(parse_input(in4, &student) == E_OK);
}
String matching expression is based on this answer.
I would use this pseudo-code:
isolate first comma-separated token
if no token, return 1
if length >= sizeof(s.name), return 1
copy first token to s.name
isolate second token
if no token, return 2
if length >= sizeof(s.surname), return 2
copy first token to s.surname
isolate third token
if no token, return 3
if token not numeric, return 3
set s.index = atoi( third token )
return 0
If you code that up in C, you should end up with something nice and short and clean and reliable, without too many annoyingly redundant checking and backtracking.
(Actually, if it was me, I'd use one general-purpose function to do the token isolating all at once, up front, then simply test if the number of found tokens was 0, 1, 2, 3, or more than 3. See this web page for additional ideas.)
Here what you need if beyond what the scanf
familly functions can do, because you want to strip blank characters at the beginning or the end of a name but still want to allow spaces inside a name. IMHO, you should use a dedicated function for that parsing. Code could be:
/* Find a string delimited with a character from delims.
* Blanks at the beginning or the end of the string will be trimed out
* At return, *end points one past the end of the string and
* *next points after the delimiter (so delimiter will be next[-1])
* Returns a pointer to the beginning of the string, or NULL if
* no delimiter was found
* */
const char* find(const char * start, char **end, char **next, const char *delims) {
static const char blanks[] = " \t\r";
start += strspn(start, blanks); // trim blanks at the beginning
*end = strpbrk(start, delims); // search first delimiter
if (end == NULL) {
return NULL;
}
*next = *end + 1; // next find will start after the delimiter
while(*end > start) { // trim blanks at the end
bool found = false;
for (int i=0; i<sizeof(blanks); i++) {
if ((*end)[-1] == blanks[i]) {
--*end ;
found = true;
break;
}
}
if (! found) break;
}
return start;
}
// parse a line to fill a student_t
struct student_t* getstruct(struct student_t *p, int *err_code) {
char buffer[1024];
*err_code = 1; // be prepared to worst case
*buffer = '\0';
if (fgets(buffer,1024, stdin)!=NULL)
{
char *end, *next;
const char delims[] = ",\r\n";
const char *name = find(buffer, &end, &next, delims) ; // returns pointer to the beginning of the token
if (name && (next[-1] == ',')) { // control the delimiter
int l = end - name;
if (l > 19) l = 19;
memcpy(p->name, name, l);
p->name[l] = '\0';
*err_code = 2; // Ok, we have a name followed with a comma
const char *surname = find(next, &end, &next, delims);
if (surname && (next[-1] == ',')) { // control delimiter
int l = end - surname;
if (l > 19) l = 19;
memcpy(p->surname, surname, l);
p->surname[l] = '\0';
*err_code = 3; // Ok, we have a name followed with a comma
if (*end != ',') return NULL;
const char *index = find(next, &end, &next, delims);
if (index) { // no need to control the delimiter: scanf will control
char dummy[2]; // that there is no non blank char after the index
if (1 == sscanf(index, "%d%1s", &(p->index), dummy)) {
*err_code = 0;
}
}
}
}
}
return (*err_code == 0) ? p : NULL;
}