Formatted input from string in c++

1.9k Views Asked by At

I am making a statistics collector that reads the log of a music player and lets the user show top ten most played etc. As a noob project.

A line from the log looks like: "20:42:03 start E:\ROTATION\A\Håkan Lidbo - Dammlunga.mp3"

I have put this in a string using ifstream and getline.

Then making an array of chars of the string using

const char *charveqtur = newline.c_str();

Then I tried to sort i out with sscanf:

sscanf (charveqtur, "%d:%d:%d\tstart\t%s", &this->hour, &this->minute, &this->second, &this->filename);

The problem is that the filename is cut at the first space. I have also tried using istringstream instead but no breakthrough so far.

Which is the most convinient way of doing this? Thanks.

2

There are 2 best solutions below

2
On

You can use some input stream to read the first integers and colons, and because the filename is the last entity, you can then use std::getline. However, even if your filename is not the last part, note that std::getline is quite a versatile function that accepts any delimiter.

A more advanced method would be to define your own type for filenames and overload operator>>(std::istream &, T const &) on it.

Here is a complete example using std::getline and stringstream with basic diagnostics and some reformatting:

#include <sstream>  // for istringstream
#include <iostream> // for cout and cerr
#include <iomanip>  // for setprecision
#include <cmath> 

bool read (std::string const &line) {
    char c = 0;
    double length;
    double rating;
    std::string title;

    std::istringstream ss;
    ss.str (line);
    ss >> length;        
    if (!ss.good())    { std::cerr << "invalid length\n"; return false; }
    if (ss.get()!=':') { std::cerr << "expected colon\n"; return false; }
    ss >> rating;
    if (!ss.good())    { std::cerr << "invalid rating\n"; return false; }
    if (ss.get()!=':') { std::cerr << "expected colon\n"; return false; }
    std::getline (ss, title);


    double sink;
    std::cout << title << " (" 
              << int(length) << ':' << 60*std::modf (length,&sink)
              << " min), your rating: " << rating << '\n';

    return true;
}

int main () {
    read ("30.25:5:Vivaldi - The four seasons.ogg");
    read ("3.5:5:Cannibal Corpse - Evisceration Plague.ogg");
    read ("meh");

    return 0;
}

Output:

Vivaldi - The four seasons.ogg (30:15 min), your rating: 5
Cannibal Corpse - Evisceration Plague.ogg (3:30 min), your rating: 5
invalid length

Important: When parsing, you are sailing close to the security risks. Always be conscious and sensible and try to use tested and proven libraries where possible. This also implies that you do not use sscanf, which is not typesafe, error-prone and sometimes hard to get right.

Don't use C if you have C++, and used correctly, iostreams are even more convenient than printf/scanf+co.

0
On

You could perhaps do something like

int lastpos = 0;
if sscanf (charveqtur, "%d:%d:%d\tstart\t%n", &this->hour, 
           &this->minute, &this->second,
           &lastpos) > 3 && lastpos >0) {
    std::string filename = newline.substr(lastpos);
    /* do something with filename */
}