Call me crazy if you will (after all, I am), but I'd do most of this with sscanf
:
char const format[] = "%11[0-9]%1*[\t]%30[^\t]%1*[\t]%30[^\t]%1*[\t]%30[^\t]%1*[\t]";
if (4 == sscanf(string, format, ID, first_name, last_name, city) && strlen(ID) == 11)
// valid data
So let's tear apart that format string into the individual pieces, so maybe they're not quite so...opaque.
It's really a number of separate pieces:
%11[0-9]
This says read (up to) 11 digits. It's not as well known as (I think) it deserves, but scanf
supports a small but useful subset of regular expressions in what are called scan-set conversions. Technically, this one has undefined behavior--the standard doesn't require that 0-9
actually means the same as 0123456789
. If you look really hard, you can find a compiler (Borland on MS-DOS) that will actually interpret this as matching the three characters 0
, -
and 9
. If you really have a reason to care about ancient compilers, you'll want to use 0123456789
explicitly instead.
%1*[\t]
This says to read only one character, which can only be a tab. The *
means "don't assign the result to a variable", so we're just verifying that what's there is a tab character.
Note that this is some extra complexity because of a minor oddity in how scanf (and company) work in general. If you were using a non-whitespace character as your separator (e.g., :
) you could check for a match by just putting that character in the format string. For example, scanf("(%d:%d)", &a, &b);
will only match two numbers inside of parentheses and separated by a colon.
Whitespace is special though: any whitespace (space, tab, vertical tab, new-line, carriage return) in the format string will match an arbitrary amount of whitespace in the input (so, for example, one space can match two tabs followed by a new-line, three more tabs, and a carriage return).
So, the scan-set is just to prevent that special white-space behavior from kicking in here, which could allow (for example) four spaces as a separator instead of the tab that you want.
%30[^\t]
This says to read up to 30 characters (feel free to increase if necessary), that cant't contain a tab. Unlike the -
in the previous scan set, the ^
at the beginning of the scan set is defined to mean "not any of", just like in a normal regular expression.
From there, we basically just repeat the last two conversions a few more times to read the rest of the fields.
The return from scanf
(fscanf
, vfscanf
, etc.) is the number of items that were successfully converted (and assigned). Since we're looking for four fields, we compare to four.
Once we're done with that, we have one piece of verification left: this would/will still match if the ID (the first field) is fewer than 11 characters, so we also need to verify that strlen(id) == 11
to be sure it was all correct.
Other than that, a few minor details. First, since you're not planning to modify the string, you probably want to pass a char const *
.
Second, C has had an actual Boolean type since C99, so you unless you need compatibility with ancient compilers, you might as well return a bool
instead of an int
. There room for a little variation here though. The reserved word that's directly recognized by the compiler is _Bool
. Then there's a <stdbool.h>
that defines bool
(which expands to _Bool
), true
and false
.
So, I'd write the code something like this:
#include <stdbool.h>
bool checkString(char const *string, int len) {
char ID[12];
char first_name[31];
char last_name[31];
char city[31];
char const format[] = "%11[0-9]%1*[\t]%30[^\t]%1*[\t]%30[^\t]%1*[\t]%30[^\t]%1*[\t]";
return 4 == sscanf(string, format, ID, first_name, last_name, city)
&& 11 == strlen(ID));
}
One minor addition: if you were really worried about/bothered by temporarily storing the variables holding the pieces of the string, you could actually skip all but the first and the last. You need to store the first (ID) because you want to check its length when you're done reading, and you want to save the last to be sure that everything before it converted correctly. Here you're depending on the fact that scanf
and company will simply quit if something doesn't convert correctly, so the last can succeed if and only if all the preceding succeeded as well.