0

See update below!

My code (now I included more):

while(getline(checkTasks, workingString)){
            countTheDays = 0;
            // Captures a clean copy of the line in tasks.dat in it's entirety, stores in workingString.
            char nlcheck;
            checkTasks.get(nlcheck);
            if(nlcheck == '\n'){
            }
            else{
                checkTasks.unget();
                //getline(checkTasks, workingString);
                // Breaks that line up into more usable pieces of information.
                getline(check2,tName, '\t');
                getline(check2,tDate, '\t');
                getline(check2,trDate, '\t');
                getline(check2,tComplete, '\n');
                // Converts the string form of these pieces into usable integers.
                stringstream(tDate.substr(0,tDate.find_first_of('/'))) >> year;
                stringstream(tDate.substr(tDate.find_first_of('/')+1,tDate.find_last_of('/'))) >> month;
                stringstream(tDate.substr(tDate.find_last_of('/')+1,tDate.length()-1)) >> day;
                stringstream(tComplete) >> checkComplete;
                stringstream(trDate) >> checkReminder;              

                // Adds the number of days until your task is due!
                if(year != date[0]){
                    for(int i = date[1]; i <= 12; i++){
                        countTheDays += System::DateTime::DaysInMonth(date[0], i);                      
                    }
                    countTheDays-= date[2];
                    for (int i = 1; i<= month; i++){
                        countTheDays +=System::DateTime::DaysInMonth(year, i);
                    }
                    countTheDays -= (System::DateTime::DaysInMonth(year, month) - day);
                }
                else if(month != date[1]){
                    for(int i = date[1]; i <= month; i++){
                        countTheDays += System::DateTime::DaysInMonth(date[0], i);
                    }
                    countTheDays -= (date[2]);
                    countTheDays -= (System::DateTime::DaysInMonth(year, month) - day);
                }
                else{
                    countTheDays+= System::DateTime::DaysInMonth(date[0], month);
                    countTheDays -= (System::DateTime::DaysInMonth(year, month) - day);
                    countTheDays -= date[2];                    
                }

                // If the task is nearing it's due date (the time frame specified to be notified) it'll notify user. 
                // Only coded to work if the task is due in the current or following months.
                if(countTheDays <= checkReminder){
                    if( countTheDays < 0){
                        cout << endl << endl << tName << " is past due!" << endl;
                        cout << "Should I keep reminding you about this task? Enter Y or N. ";
                        cin >> continueToRemind;
                    }
                    else{
                        cout << endl << endl << tName << " is due in " <<countTheDays << " days! Don't forget!" << endl;
                        cout << "Should I keep reminding you about this task? Enter Y or N. ";
                        cin >> continueToRemind;
                        }

                    // If user doesn't want to be reminded, begins process of converting that line in the file to usable info
                    // and 'overwriting' the old file by creating a new one, deleting the old one, and renaming the new one.
                    if(continueToRemind == "n" || continueToRemind == "N"){
                        fileModified = true;
                        string line;
                    /*  vector<string> lines;
                        while(getline(tasksClone, line)){
                            lines.push_back(line);
                        }
                        lines.erase(remove(lines.begin(), lines.end(), workingString), lines.end());
                        if (!lines.empty()) {
                            auto i=lines.begin();
                            auto e=lines.end()-1;
                            for (; i!=e; ++i) {
                                saveTasks << *i << '\n';
                            }
                            saveTasks << *i;
                        }*/



                        // This writes a copy of the tasks.dat file, minus the task that the user elected not to be notified of.'
                        while(getline(tasksClone, testBuffer)){
                            if(testBuffer == workingString){
                                // This condition does nothing. Essentially erasing the 'completed' task from the list.
                            }
                            else if(testBuffer != workingString && tasksClone.eof()){
                                // This writes everything except the specified task to taskbuffer.dat
                                saveTasks << testBuffer;
                            }
                            else { 
                                saveTasks << testBuffer << '\n';
                            }
                        }                   
                    }                       
                }
            }       
        }
    }
    else{
        cout << "The tasks file is empty, you must not have any tasks!" << endl;
    }

I hope that question makes sense!

Thanks

Marcus

UPDATE: Reworked the code, again. After telling it to delete 1 task, it never runs this loop. Current code:

while(getline(tasksClone, testBuffer)){
        if(testBuffer == workingString){
            // This condition does nothing. Essentially erasing the 'completed' task from the list.
        }
        else if(testBuffer != workingString && tasksClone.eof()){
            // This writes everything except the specified task to taskbuffer.dat
                saveTasks << testBuffer;
                            }
        else { 
                saveTasks << testBuffer << '\n';
        }

}

Input file:

test1   2012/12/13  10  0;
test2   2012/12/23  20  0;
test3   2012/12/31  28  0;
\n

Output file (after telling it to delete test1 and 2):

test2   2012/12/23  20  0;
test3   2012/12/31  28  0;
\n
smithisize
  • 1
  • 1
  • 6
  • your thought is not wrong, are you having problems with it? You might if there is an empty line at the end. – perreal Dec 03 '12 at 04:51
  • I'm indeed having issues. For some reason, after reading any of those lines, the eofbit is set! I don't know why! To be clear, I am using '\n' as a newline char. So, I don't see why that should throw .eof – smithisize Dec 03 '12 at 05:05
  • The code, as you have it now, only deletes whatever lines are equivalent to the contents of `workingString`. Where's the code where you try to delete any others? – Benjamin Lindley Dec 03 '12 at 06:48
  • @BenjaminLindley: I added more code to my example up top and changed the way my program writes new tasks, adding the newline AFTER the data instead of before. – smithisize Dec 04 '12 at 05:09

5 Answers5

0

The first thing I would do is just read your whole file into a vector of strings, each string corresponding to a line in the file. That can be done quite easily.

std::vector<std::string> lines;
std::string line;
while (std::getline(tasksClone, line))
    lines.push_back(line);

Once you have that, processing becomes much easier. You can check if a line is empty like this:

if (lines[i].empty())

That's equivalent to checking if the next character after a newline is another newline. You can remove any lines from the vector that you don't want like this:

lines.erase(std::remove(lines.begin(), lines.end(), workingString), lines.end());

Instead of that though, you could avoid putting them into the vector in the first place by putting a test in the loop that reads the lines from the file.

Once you've done all your processing, you can just write the lines back out to the other file, inserting the newlines manually.

if (!lines.empty()) {
    auto i=lines.begin(), e=lines.end()-1;
    for (; i!=e; ++i) {
        saveTasks << *i << '\n';
    }
    saveTasks << *i;
}
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • I suppose I'm a bit confused. Basically, getline will read to my deliminator, which since it is unspecified is the newline character. After the eof has been reached, nothing else is read. So, how does checking if the string is empty tell me if I've reached EOF? – smithisize Dec 03 '12 at 05:31
  • @smithisize: It doesn't. When you reach EOF, the getline operation fails, and so the while loop ends, and so items stop being put into the vector. The last line of the file is the last string in the vector. The part in my answer about checking if the string was empty was just for checking if you had any empty lines, which is something you seemed to be interested in. To be honest, your requirements are not entirely clear to me. So I just presented this because it's a lot easier to process text as a vector of strings than dealing directly with a file. – Benjamin Lindley Dec 03 '12 at 05:35
  • Ok, that's what I was thinking. Basically, I need to insert a newline character after every line, unless it is the last line in the file. For instance, say I have 3 tasks. Ignoring that first newline character, assume I have three lines. At the end of the first two lines is a newline character, but not at the end of the third line. – smithisize Dec 03 '12 at 05:41
  • @smithisize: Yes, that is what the code I presented does. It outputs every line but the last with a trailing newline. Then it outputs the last line, without a trailing newline. – Benjamin Lindley Dec 03 '12 at 05:47
  • I'm getting an error with that code. I'm sure it's on my end. But here's the error: `1>src\main.cpp(167): error C2947: expecting '>' to terminate template-argument-list, found '<' 1>src\main.cpp(170): error C3538: in a declarator-list 'auto' must always deduce to the same type` All I've done is replaced the push_back in the while loop with that if loop you've written – smithisize Dec 03 '12 at 05:53
  • @smithisize: If you did that, then your code certainly won't work right. I'd need to see the actual code in order to diagnose it. – Benjamin Lindley Dec 03 '12 at 05:58
  • Yeah, I'm doing it wrong for sure. So, load the vector with the while loop. Then check if the string at position i is the one I want to delete. If so, remove it from the vector. (?). Then, use your if loop to write the lines of the vector to the file? Should it look like this? (I'm trying to understand your code, sorry for my misunderstanding): – smithisize Dec 03 '12 at 06:07
  • `string line; vector – smithisize Dec 03 '12 at 06:08
  • @smithisize: Other than what I'm sure is a typo: `vector`, yes, that's correct. Is it working for you? – Benjamin Lindley Dec 03 '12 at 06:13
  • I've corrected the typo and now get the following errors: `1>src\main.cpp(168): error C2660: 'remove' : function does not take 3 arguments 1>src\main.cpp(170): error C3538: in a declarator-list 'auto' must always deduce to the same type` – smithisize Dec 03 '12 at 06:17
  • @smithisize: For `remove`, have you included ``? For the other, what compiler and version are you using? Sounds like a compiler bug, possibly. Try declaring `i` and `e` with two separate statements: `auto i=lines.begin(); auto e=lines.end()-1;` – Benjamin Lindley Dec 03 '12 at 06:21
  • had to declare i and e separately. Now, though, if I request that it deletes 2 lines, it only deletes one. Given: test1 2012/12/30 30 0; test2 2012/12/31 31 0; test3 2012/12/15 30 0; I say delete test1 and test 2. It will only delete test 1, and save test 2. EDIT: Even if I tell it to delete all 3, it only deletes 1. – smithisize Dec 03 '12 at 06:32
  • @smithisize: This is getting hard to follow. Could you update your question with the current iteration of your code, along with a description of your problem? Don't delete the old content though. – Benjamin Lindley Dec 03 '12 at 06:33
  • If you want to do everything in memory, the simplest solution is to create a `Line` class, whose `>>` does `getline`, and use this: `std::vector v( (std::istream_iterator( input )), (std::istream_iterator()) );`. But from his description, there's really no need to read everything in memory. – James Kanze Dec 03 '12 at 09:41
  • There's a fundamental issue here. If the output is opened in text mode, the last character output _must_ be a `'\n'`; otherwise, the behavior is undefined. What he seems to be asking for simply isn't legal. – James Kanze Dec 03 '12 at 09:47
  • @JamesKanze: I would have to disagree that writing a class with a not very descriptive name, just so you can use use istream_iterator(which I find hideous), is simpler than writing a simple loop with getline and push_back. – Benjamin Lindley Dec 03 '12 at 12:51
  • @BenjaminLindley It's a class whose name says exactly what it is (a `Line`). And you're not writing it just to use `istream_iterator`, you're writing it because it best expresses the abstraction you're dealing with. I use it regularly (usually extended to remove comments and trim trailing white space). – James Kanze Dec 03 '12 at 13:43
0

Instead of adding a new-line at the end of each line, but only if there's another line to write afterwards, I'd just precede each line by a new-line. That'll get you the new-line at the beginning of the file, and no new-line at the end, just as you want.

As usual, code like while (!your_file.eof()) is broken though. You probably want something like:

while (std::getline(taskClone, buffer)) {
    if (buffer.empty()) 
        continue;

    if (keep(buffer)) // whatever condition you need
        saveTasks << "\n" << buffer;
}

You can do the same thing a little more easily with std::remove_copy_if and my ostream_prefix_iterator:

// prefix_iterator.h
#include <ostream>
#include <iterator>

template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> 
>
class prefix_ostream_iterator :
    public std::iterator<std::output_iterator_tag,void,void,void,void>
{
    charT const* delimiter;
    std::basic_ostream<charT,traits> *os;
public:
    typedef charT char_type;
    typedef traits traits_type;
    typedef std::basic_ostream<charT,traits> ostream_type;

    prefix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0) 
    {}
    prefix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d)
    {}
    prefix_ostream_iterator<T,charT,traits>& operator=(T const &item)
    {
        // Here's the only real change from ostream_iterator:
        // Normally, the '*os << item;' would come before the 'if'.
        if (delimiter != 0) 
            *os << delimiter;
        *os << item;
        return *this;
    }

    prefix_ostream_iterator<T,charT,traits> &operator*() {
        return *this; 
    }
    prefix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    }
    prefix_ostream_iterator<T,charT,traits> &operator++(int) {
        return *this; 
    }
};

Using this along with a line proxy I posted in an earlier answer, you could do the job with code something like:

#include "line.h"
#include "prefix_iterator.h"

std::remove_copy_if(std::istream_iterator<line>(taskClone), 
                    std::istream_iterator<line>(),
                    prefix_ostream_iterator<std::string>(taskSave, "\n"),
                    [](std::string const &line) { /* decide if to keep line*/});
Community
  • 1
  • 1
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • After `std::getline(taskClone, buffer)`, buffer[0] could never be '\n'. If the first character is '\n', buffer would be empty. – Benjamin Lindley Dec 03 '12 at 05:23
  • @JerryCoffin: Thanks for the reply. Going to be honest, most of that went over my head unfortunately. I'm not sure I have the knowledge to correctly implement that, but I appreciate the suggestion! – smithisize Dec 03 '12 at 05:34
  • @smithisize: The important point is pretty simple: instead of trying to add a newline to the *end* of a line only if there's more data after it, just put a newline *before* every line (and ignore the initial empty line when reading). – Jerry Coffin Dec 03 '12 at 05:38
  • @JerryCoffin: So in my code, comment out if(newline == '\n'){ //saveTasks << '\n'; } like that and add `else{ saveTasks << '\n' << testBuffer;}` – smithisize Dec 03 '12 at 05:48
  • @JerryCoffin: that would make sense, but when I do that, it for some reason doesn't always delete lines I want it to. I will request that it delete (assuming we have a 3 line file, all with data, not beginning with a newline) lines 2 and 3. It deletes line 2, but not line 3. It's wonky :/ – smithisize Dec 03 '12 at 05:56
  • That's probably because of your `while (!file.eof())`, which will typically make it seem like the last line was read twice. Switch to something like `while (getline(file, buffer)) ...` and see if things don't get better. – Jerry Coffin Dec 03 '12 at 05:59
  • @JerryCoffin: That has the opposite affect, deleting two lines instead of one. – smithisize Dec 03 '12 at 06:14
  • Putting the `'\n'` at the start of the line is very bad procedure; a file opened in text mode _must_ end with a `'\n'` character, or the behavior is undefined. – James Kanze Dec 03 '12 at 09:49
  • @JamesKanze: While I agree in general, that's the format he says he wants. – Jerry Coffin Dec 03 '12 at 14:16
  • @JerryCoffin If he absolutely needs a file which doesn't end in a `'\n'`, he will have to write it in binary mode, handling line end conventions, etc. himself. C++ doesn't allow writing a text file whose last character is _not_ `'\n'`. (It would be nice if Windows stated clearly somewhere whether the CRLF is a line terminator, or a line separator. All of the C++ implementations I know treat it as a terminator, but most other programs treat it as a separator.) – James Kanze Dec 03 '12 at 14:44
  • @JamesKanze: We both know that in theory, you're absolutely right. We also both know that the chances are *extremely* good that he's on a system where a file is simply a stream of bytes, and he'll be just fine without jumping through flaming hoops. – Jerry Coffin Dec 03 '12 at 15:09
  • @JerryCoffin We also both know that there's almost certainly no reason for him to _not_ put the final `'\n'` where it belongs, and that it is much better programming practice to do so. – James Kanze Dec 03 '12 at 19:10
  • @JerryCoffin: I modified my code to put the newline after the data, instead of before. It's behaving better, but still won't delete all the requested data. I added more code to show how I'm iterating through the file. – smithisize Dec 04 '12 at 05:10
0

It's not too clear what you are trying to do. std::getline strips the trailing new line, so any time you output what you've read, you have to add it. (If you're writing to a text file, it's undefined behavior if the last character written isn't a '\n'.)

Note too that your main loop is incorrect. The state of tasksClone.eof() is indeterminate, and you don't verify that the input has succeeded after doing the getline. What you need is something like:

std::string line
while ( std::getline( tasksClone, line ) ) {
    if ( line != workingString ) {
        saveTasks << line << '\n';
    }
}

In your example input, the first line you read will be empty.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
0

I solved my problem, just in case anyone wants to know. It's not as memory efficient as it could be, but it functions:

// Reads the file, checks if it's due, and if it's due prompts user to either save or delete the task.
// Captures a clean copy of the line, in its entirety, and stores it to workingString.
while(getline(checkTasks,workingString)){
    countTheDays = 0;
    // Handles newline characters.
    char nlcheck;
    checkTasks.get(nlcheck);
    if(nlcheck == '\n'){
    }
    else{
        checkTasks.unget();
        // Breaks that line up into more usable pieces of information.
        getline(check2,tName, '\t');
        getline(check2,tDate, '\t');
        getline(check2,trDate, '\t');
        getline(check2,tComplete, '\n');
        // Converts the string from of these pieces into usable integers.
        stringstream(tDate.substr(0,tDate.find_first_of('/'))) >> year;                       stringstream(tDate.substr(tDate.find_first_of('/')+1,tDate.find_last_of('/'))) >> month;                  stringstream(tDate.substr(tDate.find_last_of('/')+1,tDate.length()-1)) >> day;
        stringstream(tComplete) >> checkComplete;
        stringstream(trDate) >> checkReminder;              

        // Adds the number of days until your task is due!
        if(year != date[0]){
            for(int i = date[1]; i <= 12; i++){
                countTheDays += System::DateTime::DaysInMonth(date[0], i);                      
            }
            countTheDays-= date[2];
            for (int i = 1; i<= month; i++){
                countTheDays +=System::DateTime::DaysInMonth(year, i);
            }
            countTheDays -= (System::DateTime::DaysInMonth(year, month) - day);
        }
        else if(month != date[1]){
            for(int i = date[1]; i <= month; i++){
                countTheDays += System::DateTime::DaysInMonth(date[0], i);
            }
            countTheDays -= (date[2]);
            countTheDays -= (System::DateTime::DaysInMonth(year, month) - day);
        }
        else{
            countTheDays+= System::DateTime::DaysInMonth(date[0], month);
            countTheDays -= (System::DateTime::DaysInMonth(year, month) - day);
            countTheDays -= date[2];                    
        }

        // If the task is nearing it's due date (the time frame specified to be notified) it'll notify user. 
        if(countTheDays <= checkReminder){
            if( countTheDays < 0){
                cout << endl << endl << tName << " is past due!" << endl;
                cout << "Should I keep reminding you about this task? Enter Y or N. ";
                cin >> continueToRemind;
            }
            else{
                cout << endl << endl << tName << " is due in " <<countTheDays << " days! Don't forget!" << endl;
                cout << "Should I keep reminding you about this task? Enter Y or N. ";
                cin >> continueToRemind;
            }

            // If user doesn't want to be reminded, begins process of converting that line in the file to usable info
            // and 'overwriting' the old file by creating a new one, deleting the old one, and renaming the new one.
            if(continueToRemind == "n" || continueToRemind == "N"){
                fileModified = true;
                // Adds workingString to deleteTasks[] to be compared against later.
                deleteTasks.push_back(workingString);
            }
        }
    }
}

        int match = 0;
        // Iterates through tempTasks.dat and compares lines to workingString.
        while(getline(tasksClone, testBuffer)){
            for(int i = 0; i< deleteTasks.size(); i++){
                // If a match is found, it sets the match flag to delete that line.
                if(testBuffer ==  deleteTasks[i]){
                    match = 1;
                }
            }
            // Deletes that task.
            if(match == 1){
                //Do nothing, erasing the task
            }
            // If EOF, writes only the task, without a newline character.
            else if(tasksClone.eof()){
                saveTasks << testBuffer;
            }
            // If !EOF, also writes a newline character.
            else{
                saveTasks << testBuffer << '\n';
            }
            match = 0;
        }
smithisize
  • 1
  • 1
  • 6
-1

You problem may be in using "\n" if you are viewing your files in Windows or Mac. Try to use "\r\n" instead.

Arsenii Fomin
  • 3,120
  • 3
  • 22
  • 42