tags:

views:

121

answers:

3

I've been using C++ for quite a long time now but nevertheless I tend to fall back on scanf when I have to parse simple text files. For example given a config like this (also assuming that the order of the fields could vary):

foo: [3 4 5]
baz: 3.0

I would write something like:

char line[SOME_SIZE];
while (fgets(line, SOME_SIZE, file)) {
    int x, y, z;
    if (3 == sscanf(line, "foo: [%d %d %d]", &x, &y, &z)) {
        continue;
    }
    float w;
    if (1 == sscanf(line, "baz: %f", &w)) {
        continue;
    }
}

What's the most concise way to achieve this in C++? Whenever I try I end up with a lot of scaffolding code.

A: 

I think Boost.Spirit is a good way to describe a grammar right in your C++ code. It takes some time to get used to Boost.Spirit but after it is quite easy to use it. It might not be as concise as probably you want but I think it is a handy way of handling simple grammars.Its performance might be a problem so it is likely that in situations where you need speed it might be not a good choice.

skwllsp
I've actually used spirit to parse very complicated structures but I think it's overkill for the given example, and will also need a lot of scaffolding code to set up.
Andreas Brinck
+3  A: 

Most of the time I use a combination of std::istringstream and std::getlines (which can work to separate words) to get what I want. And if I can I make my config files look like:

foo=1,2,3,4

which makes it easy.

text file is like this:

foo=1,2,3,4
bar=0

And you parse it like this:

int main()
{
    std::ifstream file( "sample.txt" );

    std::string line;
    while( std::getline( file, line ) )   
    {
        std::istringstream iss( line );

        std::string result;
        std::getline( iss, result , '=');

        if( result == "foo" )
        {
            std::string token;
            while( std::getline( iss, token, ',' ) )
            {
                std::cout << token << std::endl;
            }
        }
        if( result == "bar" )
        {
           //...
    }
}
Nikko
Your second call to `std::getline()` badly misses a check whether it succeeded. If it wasn't for this, you'd have my vote.
sbi
+1  A: 

I feel your pain. I regularly deal with files that have fixed width fields (output via Fortran77 code), so it is always entertaining to attempt to load them with the minimum of fuss. Personally, I'd like to see boost::format supply a scanf implementation. But, barring implementing it myself, I do something similar to @Nikko using boost::tokenizer with offset separators and lexical cast for conversion. For example,

typedef boost::token_iterator_generator< 
                                boost::char_separator<char> >::type tokenizer;

boost::char_separator<char> sep("=,");

std::string line;
std::getline( file_istream, line );
tokenizer tok = boost::make_token_iterator< std::string > (
                                line.begin(), line.end() sep );

std::string var = *tok;  // need to check for tok.at_end() here
++tok;

std::vector< int > vals;
for(;!tok.at_end();++tok){
 vals.push_back( boost::lexical_cast< int >( trimws( *tok ) );
}

Note: boost::lexical_cast does not deal well with leading whitespace (it throws), so I recommend trimming the whitespace of anything you pass it.

rcollyer