Jared
Jared

Reputation: 91

C++ editing text file using ofstream

I am currently trying to manipulate the text of a text file (without using vectors):

(This is for an assignment for an engineering paper, and I'm not allowed to use vectors)

Variable numRows can be any integer (I chose 3 just for this example) and is a square matrix of elements in my output text file 'example.txt'

Here is my code:

#include <iostream>
#include <fstream>
using namespace std;

int main () {

int numRows = 3;

  ofstream myfile ("example.txt");
  if (myfile.is_open())
  {
    for (int i = 0; i < numRows; i++) {
    for (int j = 0; j < numRows; j++) {
    myfile << "Test\t";
    } 
    myfile << "\n";
    }
    myfile.close();
  }

  else cout << "Unable to open file";

  return 0;
}

The text file is as follows:

Test    Test    Test    
Test    Test    Test    
Test    Test    Test    

My question is, once I've done all of this - how do I open it again and edit it? Eg. say I wanted to edit row 2, column 2 to "Hello":

Test    Test    Test    
Test    Hello   Test    
Test    Test    Test    

How would I do this? I need to be able to manipulate the strings stored in this text file and am unsure of what to do since I have separated each string by the tab character

Thank you for taking the time to help!

Upvotes: 3

Views: 5777

Answers (2)

Tony Delroy
Tony Delroy

Reputation: 106116

It's not very practical to think in terms of "editing" such a file, as most filesystems (and standard file I/O APIs) only support overwriting of file content - you can't replace some text with text of a different length and have the file adjust the position of the remaining file content accordingly. So, it's best to read the file into some variables in memory, edit it there, then write out the updated content (i.e. overwrite the entire file) when you've made all the edits you need to at that time. To read the file, you can use std::ifstream. To store the file in memory, a std::vector<std::vector<std::string>> - where the outer vectors represent rows/lines, and the inner vector column values in that row/line - seems a natural match given your problem description, but you could consider a std::vector<std::string> where each string represents a complete row/line including the tab characters.

(If you knew an upper limit to the width of the individual strings (in your example "Text", "Hello"), and were happy to pad each with spaces instead of a trailing tab, then you could overwrite the text and spaces without the file needing to be resized (as long as you weren't changing the number of lines/rows or adding/removing columns). To do that, you'd "seek" in the std::fstream to the position at which you wanted to replace a value.)

Further to the discussion in comments, if you insist on changing the text file directly, one easy option is to write an updated file as you read the original, then move that updated output file over the original afterwards. The actual generation of an updated output file could look something like:

#include <fstream>
#include <iostream>

void edit_file(int line_to_change, int column_to_change, const std::string& change_to)
{
    if (std::ifstream in{"txt_in"})
        if (std::ofstream out{"txt_out"})
        {
            int line = 1, column = 1;
            std::string word;
            char whitespace;
            while (in >> word && in.get(whitespace))
            {
                if (line == line_to_change && column == column_to_change)
                    word = change_to;
                out << word << whitespace;
                if (whitespace == '\n') // newline...
                {
                    ++line;
                    column = 1;
                }
                else // presumably a tab...
                    ++column;
            }
        }
        else
            std::cerr << "unable to open output file\n";
    else
        std::cerr << "unable to open input file\n";
}

int main()
{
    edit_file(2, 2, "Hello");
}

Notes:

  • in.get(whitespace) is used instead of >>, as the latter skips whitespace and that's exactly what we need to read in order to differentiate tabs from newlines

  • sending output to a distinct file prevents hassles when you're inserting longer "words" than the file previously contained, which if done naively would overwrite the input you'd be about to write, and if done properly requires an arbitrary amount of buffering in memory

Upvotes: 1

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153840

On typical file systems you can't insert characters at arbitrary posistions. If you want "edit" a file in-place you'd use a format where each record takes a fixed amount of space. Of course, the size of each record would be bounded by the size of this fixed amount. For example you could use 10 bytes per record, limiting the maximum size to 10. If a record is shorter you'd either pad it out with some byte which can't happen (e.g., tabs) or you use a suitable number of bytes at a fixed location of the record to specify the size if the used string. For up to 255 bytes one extra byte would be used.

Since the records would have a fixed size you could seek to the location of the modified record: the start would be (row * columns + column) * record_size. You'd just write the new value (and if you choose to save the size, the size of the string in the record).

If you need to support variable length strings without an upper bound or you need to use a compact representatio the above approach doesn't work. You'll need to entirely rewrite the file. To that you'd

  1. Open the input file for reading.
  2. Open a temporary file for writing.
  3. Read each unchanged record prio to the chang and write it to the temporary file.
  4. Read the changed record and do nothing with it.
  5. Write the new value to the temporary file.
  6. Copy the remainder of the input file to the temporary file.
  7. Close both files.
  8. Rename the temporary file to the source file name.

Obviously, if you need to do multiple edits at a time you would continue copying/replacing as needed. The approach would stay pretty much the same, though.

I could easily write the code for your homework assignment but the above already removed all thinking from said assignment. Also, I don't really need to practice programming so I'll skip providing code.

Upvotes: 3

Related Questions