user3118602
user3118602

Reputation: 583

Ignoring empty line during fstream

Writing a program to read a text file and storing it in a struct. An example of the text file:

chicken

dog

car


765

When there is some text in the line, it will get store into the struct. I have tried the following:

getline(file, aLine);
Info.animalchicken = aLine;

getline(file, aLine);
Info.animaldog = aLine;

getline(file, aLine);
Info.car = aLine;

getline(file, aLine);
Info.number = aLine;

I realised that the getline is literally getting every single line. When I run this in my program, the chicken will be stored in the struct Info.animalchicken. The next line, which is empty, will store into Info.animaldog. Dog will be stored in Info.car and so on.

I think a control loop is required here but can't think of a good one. How can I ignore the empty line so my text can enter into the struct correctly?

This is my struct

struct Info {
    string animalchicken;
    string animaldog;
    string car;
    int number;
}

Upvotes: 0

Views: 1022

Answers (3)

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

Here's an option adding stream operators and a helper function to skip empty lines.

#include <iostream>
#include <limits>
#include <sstream>
#include <string>

struct Info {
    std::string animalchicken;
    std::string animaldog;
    std::string car;
    int number;
};

// a helper function to do getline but skip empty lines
std::istream& getline_with_content(std::istream& is, std::string& s) {
    while(std::getline(is, s)) if(not s.empty()) break;
    return is;
}

// an istream operator to read one Info
std::istream& operator>>(std::istream& is, Info& i) {
    getline_with_content(
        getline_with_content(
            getline_with_content(is,
                                 i.animalchicken),
            i.animaldog),
        i.car);

    is >> i.number;

    // ignore everything after the number until a newline appears:
    is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    return is;
}

// an ostream operator to print one Info
std::ostream& operator<<(std::ostream& os, const Info& i) {
    return os << i.animalchicken << '\n'
              << i.animaldog << '\n'
              << i.car << '\n'
              << i.number << '\n';
}

int main() {
    // an example istream with a lot of blank lines:
    std::istringstream file(
        "chicken\n\n"
        "dog\n\n"
        "car\n\n\n"
        "765\n");

    Info i;

    file >> i;      // read one Info from the stream

    std::cout << i; // print one Info
}

Demo

Upvotes: 0

Bartek Banachewicz
Bartek Banachewicz

Reputation: 39380

The loop idea, while quite primitive, should do the trick; the easiest way would be to wrap the logic in a separate function:

std::string getlineFilterEmpty(std::istream& s) {
    std::string line;

    do {
        if (!s) {
            throw std::runtime_error("End of stream");
        }
        getline(s, line);
    } while(line.size() == 0);

    return line;
}

Then getting your values is as simple as:

Info.animalchicken = getlineFilterEmpty(file);
Info.animaldog = getlineFilterEmpty(file);
Info.car = getlineFilterEmpty(file);

The number member will require parsing the string to an integer, the code for which you'll find elsewhere on SO.

Upvotes: 1

user4581301
user4581301

Reputation: 33932

The logic needs to go something like,

Read a line. 
If read succeeded
    If line not empty
        Provide line
    Else
        Try again
Else
    Handle error

Translating that into code and bundling it into a function for easy reuse, we get

std::string getNotEmptyLine(std::istream & in)
{
    while (true) // repeat forever!
    {
        std::string temp;
        std::getline(in, temp); // get a line
        if (in) // test the line
        {
            if (line.size() != 0) // line not empty
            {
                 return temp; //give it to caller
            }
        }
        else
        {
            // handle error. We'll throw an exception, but this isn't the best solution
            throw std::runtime_error("Couldn't read a line!"); 
        }
    }
}

As with all literal translations, it needs a bit of work. It would also be helpful to make this function work exactly like getline so the caller can use it as a drop-in replacement.

std::istream & getNotEmptyLine(std::istream & in, // stream to read
                               std::string & line, // somewhere to put the string 
                               char  delim = '\n') // allow different delimiters
{
    while (true) // repeat forever!
    {
        if (std::getline(in, line, delim)) // get a line right in line and test that we got it.
        {
            if (line.size() != 0) // line not empty
            {
                 break; // success. exit.
            }
        }
        else
        {
            // line will contain whatever this implementation of `getline` puts or 
            // leaves in the string on failure.
            break; // fail. Let the caller decide what to do
        }
    }
    return in;
}

Usage:

Info info;
std::string aLine;
if (getNotEmptyLine(in, info.animalchicken) &&
    getNotEmptyLine(in, info.animaldog) &&
    getNotEmptyLine(in, info.car) &&
    getNotEmptyLine(in, aLine))
{
    info.number = std::stoi(aLine);
}
else
{
    // handle error
}

Note: even this may be too simplistic. It can't handle a line that contains nothing but whitespace. A single misplaced and nigh-invisible space will wreak havoc. If this is a concern, add more logic to if (line.size() != 0)

Upvotes: 0

Related Questions