pellerusty
pellerusty

Reputation: 3

Sscanf not returning what I want

I have the following problem: sscanf is not returning the way I want it to. This is the sscanf:

sscanf(naru,
       "%s[^;]%s[^;]%s[^;]%s[^;]%f[^';']%f[^';']%[^;]%[^;]%[^;]%[^;]"
       "%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]"
       "%[^;]%[^;]%[^;]%[^;]%[^;]%[^;]",
       &jokeri, &paiva1, &keskilampo1, &minlampo1, &maxlampo1,
       &paiva2, &keskilampo2, &minlampo2, &maxlampo2, &paiva3,
       &keskilampo3, &minlampo3, &maxlampo3, &paiva4, &keskilampo4,
       &minlampo4, &maxlampo4, &paiva5, &keskilampo5, &minlampo5,
       &maxlampo5, &paiva6, &keskilampo6, &minlampo6, &maxlampo6,
       &paiva7, &keskilampo7, &minlampo7, &maxlampo7);

The string it's scanning:

const char *str = "city;"
                  "2014-04-14;7.61;4.76;7.61;"
                  "2014-04-15;5.7;5.26;6.63;"
                  "2014-04-16;4.84;2.49;5.26;"
                  "2014-04-17;2.13;1.22;3.45;"
                  "2014-04-18;3;2.15;3.01;"
                  "2014-04-19;7.28;3.82;7.28;"
                  "2014-04-20;10.62;5.5;10.62;";

All of the variables are stored as char paiva1[22] etc; however, the sscanf isn't storing anything except the city correctly. I've been trying to stop each variable at ;. Any help how to get it to store the dates etc correctly would be appreciated.

Or if there's a smarter way to do this, I'm open to suggestions.

Upvotes: 0

Views: 521

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 753705

There are multiple problems, but BLUEPIXY hit the first one — the scan-set notation doesn't follow %s.

Your first line of the format is:

"%s[^;]%s[^;]%s[^;]%s[^;]%f[^';']%f[^';']%[^;]%[^;]%[^;]%[^;]"

As it stands, it looks for a space separated word, followed by a [, a ^, a ;, and a ] (which is self-contradictory; the character after the string is a space or end of string).

The first fixup would be to use scan-sets properly:

"%[^;]%[^;]%[^;]%[^;]%f[^';']%f[^';']%[^;]%[^;]%[^;]%[^;]"

Now you have a problem that the first %[^;] scans everything up to the end of string or first semicolon, leaving nothing for the second %[;] to match.

"%[^;]; %[^;]; %[^;]; %[^;]; %f[^';']%f[^';']%[^;]%[^;]%[^;]%[^;]"

This looks for a string up to a semicolon, then for the semicolon, then optional white space, then repeats for three items. Apart from adding a length to limit the size of string, preventing overflow, these are fine. The %f is OK. The following material looks for an odd sequence of characters again.

However, when the data is looked at, it seems to consist of a city, and then seven sets of 'a date plus three numbers'.

You'd do better with an array of structures (if you've worked with those yet), or a set of 4 parallel arrays, and a loop:

char jokeri[30];
char paiva[7][30];
float keskilampo[7];
float minlampo[7];
float maxlampo[7];

int eoc;   // End of conversion
int offset = 0;
char sep;
if (fscanf(str + offset, "%29[^;]%c%n", jokeri, &sep, &eoc) != 2 || sep != ';')
    ...report error...
offset += eoc;

for (int i = 0; i < 7; i++)
{
    if (fscanf(str + offset, "%29[^;];%f;%f;%f%c%n", paiva[i],
               &keskilampo[i], &minlampo[i], &maxlampo[i], &sep, &eoc) != 5 ||
        sep != ';')
        ...report error...
    offset += eoc;
}

See also How to use sscanf() in loops.

Now you have data that can be managed. The set of 29 separately named variables is a ghastly thought; the code using them will be horrid.

Note that the scan-set conversion specifications limit the string to a maximum length one shorter than the size of jokeri and the paiva array elements.


You might legitimately be wondering about why the code uses %c%n and &sep before &eoc. There is a reason, but it is subtle. Suppose that the sscanf() format string is:

"%29[^;];%f;%f;%f;%n"

Further, suppose there's a problem in the data that the semicolon after the third number is missing. The call to sscanf() will report that it made 4 successful conversions, but it doesn't count the %n as an assignment, so you can't tell that sscanf() didn't find a semicolon and therefore did not set &eoc at all; the value is left over from a previous call to sscanf(), or simply uninitialized. By using the %c to scan a value into sep, we get 5 returned on success, and we can be sure the %n was successful too. The code checks that the value in sep is in fact a semicolon and not something else.

You might want to consider a space before the semi-colons, and before the %c. They'll allow some other data strings to be converted that would not be matched otherwise. Spaces in a format string (outside a scan-set) indicate where optional white space may appear.

Upvotes: 2

Jurlie
Jurlie

Reputation: 1014

I would use strtok function to break your string into pieces using ; as a delimiter. Such a long format string may be a source of problems in future.

Upvotes: 1

Related Questions