replace text in middle of the file - c++

I have .txt file and there are lines:
username1:123456789:etc:etc:etc:etc
username2:1234:etc:etc:etc:etc
username3:123456:etc:etc:etc:etc
username4:1234567:etc:etc:etc:etc
username1 - username;
123456789 - password;
etc - more text.
I have code to read file and find line where is username what I need. Also code change password, but there is problem if new password is longer that old one then it looks like:
username3:11111111111tc:etc:etc:etc
if new password is shorter then it looks like:
username1:111111789:etc:etc:etc:etc
I have length of new password, but how can I get length of old one and replace it properly?
My code
include<iostream>
#include<fstream>
#include <cstring>
using namespace std;
int main() {
int i=0;
bool found = false;
string line, username;
char newpass[255] = "555555555555555";
long length, plen;
cout<<"Insert username: ";
cin>>username;
username+=":";
fstream changeLINE("/.../testaDoc.txt");
if (!changeLINE) {
cout << "Can't find the file or directory!" << endl;
}
else
while (getline(changeLINE, line) && !found) {
i++;
if (line.find(username) != string::npos) {
length = changeLINE.tellg();
changeLINE.seekg((length - line.length()) + username.length() - 1);
changeLINE.write("", strlen(newpass));
cout << line << " line " << i << endl;
found = true;
}
}
if (!found)
cout << "User with username = " << username << " NOT FOUND!";
changeLINE.close();
}
I'm working on Linux, writing in C++.
Edit
Maybe there are way to add string in text, but not replace it and also way to erase string by not replacing it? Then I could read length of old password compare it with new password and delete/add letters in string to replace it properly.

Unless the line you want to replace is the same length as the new one, you need a different strategy-
This is a strategy you can use:
Create temporary file
Write the lines you dont want to change straight to the temporary file. The line you want to change, you replace with the new line
close both files.
delete original file
rename temporary file to the original files name
Or you could as mentioned in the comments read all lines into memory, e.g. a vector of lines, replace the one you want to change, and then write all lines back into the file, replacing the former content. If the new line is shorter than the previous, you can trunctate the file using e.g: How to truncate a file while it is open with fstream
Could this work:
std::vector<std::string> lines;
while (std::getline(changeLINE, line))
{
i++;
if (line.find(username) != std::string::npos) {
std::cout << line << " line " << i << std::endl;
found = true;
std::string newline = username + ":" + newpass + line.substr(line.find(":", username.length() + 2)) ;
lines.push_back(newline);
}
else
{
lines.push_back(line);
}
}
changeLINE.close();
std::ofstream ofs;
ofs.open("/.../testaDoc.txt", std::ofstream::out | std::ofstream::trunc);
for(auto& s: lines)
ofs << s << std::endl;
ofs.close();

Related

Read every line from text file to a vector C++

I have a text file where I need to be able to add or delete certain lines using functions. Everything is read from the file so when I open the file and write something then it deletes everything else in that file. I've understood that this can be done by using vectors. As I am new to C++ and especially vectors, I haven't figured out how I can read every line to a vector and then rewrite the lines to the text file.
Maybe someone can recommend me some kind of web-page or sth where I could learn how to do it.
Function for adding a line so far but it does not add the new line.
It should read the existing lines in the text file to vector<string> lines and then output it to the file while ignoring the first line with lines[i+1] and then add the new contact info to the end outside of the for loop.
void add contact(string filename, string*& names, string*& emails,
string*& numbers, unsigned int& quantity, string name, string email,
string number){
string str;
vector<string> lines;
ifstream input(filename);
while(getline(input, str)){
lines.push_back(str);
}
input.close();
ofstream output("contacts.txt");
output << quantity;
for(unsigned int i = 0; i < quantity; i++){
output << endl << lines[i+1];
}
output << endl << name << " | " << email << " | " << number << endl;
}
It's not that tough. You just have to get each line and push it to the std::vector.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
int main()
{
std::string str ;
std::vector<std::string> file_contents ;
std::fstream file;
file.open("test.txt",std::ios::in);
while(getline(file, str))
{
file_contents.push_back(str) ;
}
// You can access it using vector[i]
}

Multiset: Problem with multiset adding more than one version of a word and cannot handle large amounts of text

Update and fixed: I have fixed the problem causing the error message- Huge thanks to user PaulMcKenzie for helping me understand what the error message was telling me!- When my program encountered a letter with a mark above it (diacritical marks I think they are called), it crashed. I have adjusted my code to account for these and now it doesn't crash at all! Another huge thanks to user ihavenoidea for helping me understand multisets! My program is now working the way it's supposed to!
Original post:
****I am VERY new to C++ so any and all help is appreciated!****
Ok, so I'm trying to use multiset to sort words so I can see how many times a word appears in a text. First, my program accepts a file, then it reads the words and takes out any punctuation, then it puts it into a multiset. After this, it is supposed to put the results into a text file the user names themselves.
My first issue is that the multiset seems to be creating more than one element for the same word (For example: in one of my tests I saw a(4) listed in the text document 3 times in a row instead of one time).
My Second issue is that when I try to read in large text documents (I'm using John Colliers story "Bottle Party" http://ciscohouston.com/docs/docs/greats/bottle_party.html to test it) my program completely crashes but doesn't crash when I test it with a smaller text document (small being with say about 5-10 lines of text). I'm using Visual Studios and (once again I'm new to Visual Studios also) I don't know what the error message is trying to tell me but it says:
After selecting retry:
As always, any and all help is greatly appreciated.
Code here:
#include <iostream>
#include <string> //for strings
#include <fstream> //for files
#include <set> //for use of multiset
using namespace std;
string cleanUpPunc(string);
//Global variables
multiset <string> words; //will change back to local variable later
int main() {
//Starting variables
string fileName1 = "", fileName2 = "", input = "", input2 = ""; //To hold the input file and the file we wish to print data to if desired
ifstream fileStream; //gets infor from file
//Program start
cout << "Welcome to Bags Program by Rachel Woods!" << endl;
cout << "Please enter the name of the file you wish to input data from: ";
getline(cin, fileName1);
//Trys to open file
try {
fileStream.open(fileName1);
if (!fileStream) {
cerr << "Unable to open file, please check file name and try again." << endl;
system("PAUSE");
exit(1);
}
while (fileStream >> input) {
input2 = cleanUpPunc(input); //sends the input word to check for punctation
words.insert(input2); //puts the 'cleaned up' word into the multiset for counting
}
fileStream.close();
//Sends it to a text document
cout << "Please name the file you would like to put the results into: ";
getline(cin, fileName2);
ofstream toFile; //writes info to a file
//Code to put info into text file
toFile.open(fileName2);
if (toFile.is_open()) {
multiset<string>::iterator pos;
for (pos = words.begin(); pos != words.end(); pos++) {
toFile << *pos << " " << words.count(*pos) << endl;
}
toFile.close();
cout << "Results written to file!" << endl;
}
else {
cout << "Could not create file, please try again." << endl;
}
}catch (exception e) {
cout << "Stop that. ";
cout << e.what();
}
cout << "Thanks for using this program!" << endl;
system("PAUSE");
return 0;
}
string cleanUpPunc(string maybe) {
//Takes out puncuation from string
//Variables
string takeOut = maybe;
//Method
for (int i = 0, len = maybe.size(); i < len; i++) {
if (ispunct(takeOut[i])) {
takeOut.erase(i--, 1);
len = takeOut.size();
}
}
return takeOut;
}

Text doesn't save into the file

Hello I do a program and in my program I have a class Customer.
In order to save the customer on the computer I create a file and separate every data of customer with :: like name::password::phonenbr. But my problem is if I write the line that is in the comment on my code the data will be save into the file, but If I write the same line in the if() that checks if t the file is empty this doesn't do anything although that I see with the compiler that there is no problem with this line.
If you can help me it will be graceful !
void Shop::Add_Customer()
{
fstream myfile; myfile.open("CustomerFile.txt");
string name, password, phonenbr;
string buffer, delimitor = "::";
system("cls");
cout << "Name of the customer: "; cin >> name;
cout << "Password of the customer: "; cin >> password;
cout << "Phone number of the customer: "; cin >> phonenbr;
if (!myfile.is_open())
{
myfile.open("CustomerFile.txt", ios::out);
}
//myfile << name + delimitor + password + delimitor + phonenbr << endl;
if (myfile.peek() == std::ifstream::traits_type::eof())
{
myfile << name + delimitor + password + delimitor + phonenbr << endl;
}
else
{
while (getline(myfile, buffer))
{
if (CheckIfCustomerExist(buffer, name, phonenbr) == true)
{
cout << "Customer already exist" << endl;
}
else
{
myfile << name + delimitor + password + delimitor + phonenbr << endl;
cout << "Customer insert in the file " << endl;
}
}
}
}
The EOF flag in the stream is set when any read of the stream fails because it tried to read past the end of the stream. Once EOF is set the stream is in a bad state and cannot be read or written until the EOF flag is cleared.
Here is a really simple example of what is going on:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream myfile("CustomerFile.txt", ios::out);
if (!myfile.is_open())
{
cout << "file not open." << endl;
}
else
{
if (myfile.peek() == std::ifstream::traits_type::eof())
{
if (myfile.eof())
{
cout << "Need to clear the EOF flag." << endl;
}
}
}
}
Peeking at EOF set the EOF flag, putting the stream in an error condition and making it unwritable. Since we want to extend the file, we need to clear that flag with the aptly named clear method.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream myfile("CustomerFile.txt", ios::out);
if (!myfile.is_open())
{
cout << "file not open." << endl;
}
else
{
if (myfile.peek() == std::ifstream::traits_type::eof())
{
if (myfile.eof())
{
cout << "Need to clear the EOF flag." << endl;
}
myfile.clear();
if (!myfile.eof())
{
cout << "OK. EOF clear now." << endl;
}
}
}
}
Off topic stuff:
The following code
while (getline(myfile, buffer))
{
if (CheckIfCustomerExist(buffer, name, phonenbr) == true)
{
cout << "Customer already exist" << endl;
}
else
{
myfile << name + delimitor + password + delimitor + phonenbr << endl;
cout << "Customer insert in the file " << endl;
}
}
will repeat for every line in the file, presumably checking the input customer against every customer in the file one by one. Every time the input customer does not match, the input customer will be added to the file. This means the input customer is likely to be added to the file several times. Worse, the program is reading and writing the same file at the same time and will probably wind up corrupting the file.
It would be better to read and compare and then if a match is not found advance to the end of the file and add the input customer.
In addition, the file open logic is needlessly complicated and may still fail
fstream myfile; myfile.open("CustomerFile.txt");
if (!myfile.is_open())
{
myfile.open("CustomerFile.txt", ios::out);
}
The first call to open will certainly fail if the file is not present, forcing the second call to open. Might as well just add the ios::outto this call and be done with it. The second call top open may fail for other reasons and is not tested for success, so I recommend
fstream myfile("CustomerFile.txt", ios::out);
if (!myfile.is_open())
{
perror("file not open: ");
}
else
{
// your code goes here
}
Documentation for perror
The root of your problem lies in your if-statement's condition:
(myfile.peek() == std::ifstream::traits_type::eof())
Apperantly, your file is open in fstream mode in the line:
fstream myfile; myfile.open("CustomerFile.txt");
Now the only reason I can get for why your if-statement's condition is not met is because the file modes are different. I'm not sure whether I am right or not (feedback is welcome in the comments box), but that's a reason I can come up with.
I tried one of my own methods that always works, and it worked for your code also. I replaced the following lines in your code:
if (myfile.peek() == std::ifstream::traits_type::eof())
{
myfile << name + delimitor + password + delimitor + phonenbr << endl;
}
With these lines:
myfile.seekg (0, ios::end);
int length = myfile.tellg();
if (length == 0)
{
myfile << name + delimitor + password + delimitor + phonenbr << endl;
}
The first line myfile.seekg (0, ios::end); gets the distance between the 2 points specified in the brackets. 0 and ios::end are self explanatory; 0 is the start of the file and ios::end is the end of the file.
The second line int length = myfile.tellg(); stores the value seeked in the above line in an int variable called length. The length is the number of characters the "cursor" would have to move to get from start to end of this file (try to imagine the cursor as the blinking thing similar to the one in Microsoft Word that is in front of the word you are typing, except here, you cannot see the cursor in your text file moving from start to end).
This if-condition is pretty straightforward. If the length is zero, meaning that the cursor has to move 0 points to get from the start of the file to the end of the file, then write whatever you want to that file. This technique worked (at least it did for me).
On a side note, there are a couple of other areas where your code can improve. For example, why have you added this if-statement:
if (!myfile.is_open())
{
myfile.open("CustomerFile.txt", ios::out);
}
This code is a repetition of these lines of your code:
fstream myfile; myfile.open("CustomerFile.txt");
The .open() command already fulfills the if-statement I pointed out. If the file specified in the open() is found, then it will open that file; else it will continue to create that new file. Therefore, that if-statement is redundant and should be removed as it consumes unnecessary CPU power and slows your program down (not by a lot, but you will soon realize that every millisecond counts in running your code; efficiency is key). I would recommend you to remove that if-statement.
Another issue is your 3 variables that you accept for input. Given that they are strings, why do you use the cin >> method? Using cin will only take the first word in your sentence; in your following line:
cout << "Name of the customer: "; cin >> name;
If you enter John Doe, it will only save John to the name variable, and it will move "Doe" to the next input variable, which is password in your case. If there is no other cin, then it will ignore the words after the space. Therefore, use the following line for all your input points:
getline(cin, name);
This function will get all the words and spaces as a single sentence till the point you hit Enter, unlike cin that will only get the first word and ignore the rest of the sentence.
Finally, your phone number should be of type int. I'll leave that for you to fix as per your requirement.
Hope I answered your question and hoped my tips were helpful. Good luck!
EDIT: Another point I missed about your code was that your while loop runs for every line. This means it will check for the particular customer's name at every single line of the file. This is not what you want. You want to read every line in the file, BUT if you find the customer, then you want to terminate the function without continuing for the next line. Also, you only want to print an error statement AFTER you have read the entire file, not just a single line.
else
{
int check = 0;
while (getline(myfile, buffer))
{
if (CheckIfCustomerExist(buffer, name, phonenbr) == true)
{
cout << "Customer already exist" << endl;
check = 1;
break;
}
}
if (check == 0)
{
myfile << name + delimitor + password + delimitor + phonenbr << endl;
cout << "Customer insert in the file " << endl;
}
}
What this piece of code does is that it runs through every line, checking for the customer in that line. If that line has the customer record, then it sets an check value of type int to 1 from 0 and the break statement terminates the while loop. After reading the entire file, it moves on to an if-statement. In this statement, if the check variable still has 0, it means that the file did not have the customer, in which the new record will be added to the file.
Also, I said that the phone_number should be an int value. I take that back as upon further though and input from fellow StackOverflow users, the phone number is better suited as a string value as its format may not be stored properly as an int value (for example, 0059875 will be stored as 59875).

ifstream opens files named by cin but not when pulled from an array. Any difference between cin and a regular string definition?

TL;DR File names stored as strings in array (using new) - ifstream won't open them (perror returns "No such file or directory"). Swap out array variable with a call to the user to name the file (using cin) - ifstream opens the file. Why? How do I get the array to work?
Things to Know
All files exist in folders with naming scheme run20### where
All files are named S20###.ABC where ### is the same from the parent directory and ABC can go from 001-999. These are all text files (there are no .txt extensions though) that CAN be opened by ifstream and getline.
I'm writing a program that's going to pull information from up to 150 files. An early version I wrote had the user input the file name (using cin). ifstream took the stored name and opened the file successfully every time. Obviously, I don't want to type 150 file names in so the program stores all of the file names as strings in an array for the program to pull from. However, when it goes to open the file (in the correct path and with the correct file name and extension), the error I get from perror returns "No such file or directory." If I just do a quick swap of the variables though so that the file name comes from cin, the file opens. Why would cin work and the array version not? Is there any way to get the array to work?
I've also tried something similar where there is no array. Instead, in the for loop that would pull the files from the array, the file gets named each time.
Here's the code (sorry about the headers, couldn't get it to format right):
#include <iostream>
#include <array>
#include <string>
#include <ctime>
#include <cstring>
#include <fstream>
#include <sstream>
using namespace std;
int main() {
//--------------------------Initial setup----------------------------------
cout << "Please give the full name of the folder you would like to open in the /Users/lucas/HPS/TDCData directory" << endl << endl;
string sFolderName;
cin >> sFolderName;
// Create path. I have mine here but you'll have to change it to something you'll
// use if you want to run the code
string sPathName = "/Users/lucas/HPS/TDCData/" + sFolderName;
//----------------Create file name array------------------------------------
// Get naming base from the folder name given
string sFileBase = "S20";
for (int i = 5; i <= sFolderName.length(); i++){
sFileBase = sFileBase + sFolderName[i];
}
//Specify range since different directories have different numbers of files
cout << "Files must be named S20###.ABC" << endl;
cout << "Specify a range for ABC" << endl;
int iFloor;
int iCeiling;
cout << "Floor: " << endl;
cin >> iFloor;
cout << "Ceiling: " << endl;
cin >> iCeiling;
// Define an array to store names and then store them
string *aFiles;
int iFilesSize = iCeiling - iFloor + 1;
aFiles = new string [iFilesSize];
cout << "Array created" << endl;
for (int i = iFloor; i <= iCeiling; i++){
string name = sFileBase;
if (i < 10){
name = name + ".00" + to_string(i);
}
else if (i < 100) {
name = name + ".0" + to_string(i);
}
else {
name = name + '.' + to_string(i);
}
aFiles[i-1] = name;
}
//----------------Open each file in aFiles----------------------
for (int i = 0; i < iFilesSize; i++){
// There are two important lines of code here. The first gets sFileName from
// aFiles. The second gets sFileName from user input using cin (this is commented out).
// Obviously, none of the last section of code is needed for the second line to work.
// The first line does not work for me. The second does.
string sFileName;
//First
sFileName = aFiles[i];
//Second
//cin >> sFileName
string sFullPath = sPathName + "/" + sFileName;
cout << "Searching ... " << sFullPath << endl << endl;
//Open file
ifstream inputFile(sFullPath);
//Check that the file opened
if (! inputFile.is_open()) {
cout << "Error reading" << sFullPath << endl;
perror("Error is: ");
return 0;
}
else {
cout << "File opened successfully..." << aFiles[i] << endl << endl;
}
}
cout << "All files opened..." << endl << endl;
return 0;
}
Also here's a link to a zip of one of the directories for any tests someone might want to run. Thanks for any and all help!
It looks like you start filling aFiles from index iFloor, while you start reading aFiles from index 0.
How about changing aFiles[i-1] = name; to aFiles[i-iFloor] = name;
"TL;DR File names stored as strings in array (using new)"
Don't do this. Use a dynamic container like std::vector<std::string> instead.
"- ifstream won't open them (perror returns "No such file or directory")."
Use the debugger to check what's actually passed to the
ifstream inputFile(sFullPath);
with sFullPath.
"Swap out array variable with a call to the user to name the file (using cin) - ifstream opens the file. Why? How do I get the array to work?"
You cannot replace the behaviors of a stream getting values as you're trying with the array.
The best way to make the input stream source transparent, is to simply use a std::istream reference, and don't care if it's std::cin or e.g. a std::istringstream reference.
The std::string instance needed to initialize the mentioned std::istringstream can be build e.g. using a std::ostringstream and pass the str() property to the std::istringstream constructor.

why does vector.size() read in one line too little?

when running the following code, the amount of lines will read on less then there actually is (if the input file is main itself, or otherwise)
why is this and how can i change that fact (besides for just adding 1)?
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
// open text file for input
string file_name;
cout << "please enter file name: ";
cin >> file_name;
// associate the input file stream with a text file
ifstream infile(file_name.c_str());
// error checking for a valid filename
if ( !infile ) {
cerr << "Unable to open file "
<< file_name << " -- quitting!\n";
return( -1 );
}
else cout << "\n";
// some data structures to perform the function
vector<string> lines_of_text;
string textline;
// read in text file, line by line
while (getline( infile, textline, '\n' )) {
// add the new element to the vector
lines_of_text.push_back( textline );
// print the 'back' vector element - see the STL documentation
cout << "line read: " << lines_of_text.back() << "\n";
}
cout<<lines_of_text.size();
return 0;
}
The code you have is sound. Here's a small test case that might help:
void read_lines(std::istream& input) {
using namespace std;
vector<string> lines;
for (string line; getline(input, line);) {
lines.push_back(line);
cout << "read: " << lines.back() << '\n';
}
cout << "size: " << lines.size() << '\n';
}
int main() {
{
std::istringstream ss ("abc\n\n");
read_lines(ss);
}
std::cout << "---\n";
{
std::istringstream ss ("abc\n123\n");
read_lines(ss);
}
std::cout << "---\n";
{
std::istringstream ss ("abc\n123"); // last line missing newline
read_lines(ss);
}
return 0;
}
Output:
read: abc
read:
size: 2
---
read: abc
read: 123
size: 2
---
read: abc
read: 123
size: 2
I think I have tracked down the source of your problem. In Code::Blocks, a completely empty file will report that there is 1 line in it (the current one) in the gizmo on the status bar at the bottom of the IDE. This means that were you actually to enter a line of text, it would be line 1. In other words, Code::Blocks will normally over-report the number of actual lines in a file. You should never depend on CB, or any other IDE, to find out info on files - that's not what they are for.
Well, if the last line of your file is just '\n', you don't push it into the vector. If you want it to be there, change the loop to:
while (getline( infile, textline, '\n' ).gcount() > 0)
{
if (infile.fail()) break; //An error occurred, break or do something else
// add the new element to the vector
lines_of_text.push_back( textline );
// print the 'back' vector element - see the STL documentation
cout << "line read: " << lines_of_text.back() << "\n";
}
Use the gcount() member to check how many characters were read in the last read - this will return 1 if it only read a delimiter character.
Ok so here is an explanation that you will hopefully understand. Your code should work fine if the file we're talking about doesn't end with newline. But what if it does? Let's say it looks like this:
"line 1"
"line 2"
""
Or as a sequence of characters:
line 1\nline 2\n
This file has THREE lines -- the last one being empty but it's there. After calling getline twice, you've read all the characters from the file. The third call to getline will say oops, end of file, sorry no more characters so you'll see only two lines of text.