0

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 4
    `sscanf(naru,"%s[^;]%s[^;]...` --> `sscanf(naru,"%[^;];%[^;];...` – BLUEPIXY May 23 '14 at 21:08
  • You should check the return value of any scanf function, always. Also, long format string like that is going to be a maintenance nightmare, if it ever needs to be changed. – hyde May 23 '14 at 21:18
  • 1
    Yeesh, just split on `;` and be done with it. – Ed S. May 23 '14 at 21:25
  • Well that looks like a prime example of something that can lead to trouble. One single change in the format and you're in trouble. Please that advice from @EdS. and split it. – Andro May 23 '14 at 21:37

2 Answers2

2

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.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

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.

Jurlie
  • 1,014
  • 10
  • 27