C++ - Find and replace in text file (standard system libraries) - c++

I am looking for some advice.
My situation:
Application works with text local file.
In file are somewhere tags like this: correct = "TEXT". Unfortunatelly, there can be unlimited spaces between correct, = and "TEXT".
Obtained text is testing in function and may be replaced (the change must be stored in the file). correct = "CORRECT_TEXT"
My current theoretical approach:
With ofstream -- read by line to string.
Find tag and make change in string.
Save strings as lines to the file.
Is there some simplify way (with iterators?) in C++ with using standard system libraries only (unix).
Thank you for your ideas.

Here is a possible solution that uses:
std::getline()
std::copy()
istream_iterator
ostream_iterator
vector
Example:
#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <string>
#include <vector>
struct modified_line
{
std::string value;
operator std::string() const { return value; }
};
std::istream& operator>>(std::istream& a_in, modified_line& a_line)
{
std::string local_line;
if (std::getline(a_in, local_line))
{
// Modify 'local_line' if necessary
// and then assign to argument.
//
a_line.value = local_line;
}
return a_in;
}
int main()
{
std::ifstream in("file.txt");
if (in.is_open())
{
// Load into a vector, modifying as they are read.
//
std::vector<std::string> modified_lines;
std::copy(std::istream_iterator<modified_line>(in),
std::istream_iterator<modified_line>(),
std::back_inserter(modified_lines));
in.close();
// Overwrite.
std::ofstream out("file.txt");
if (out.is_open())
{
std::copy(modified_lines.begin(),
modified_lines.end(),
std::ostream_iterator<std::string>(out, "\n"));
}
}
return 0;
}
I am not sure exactly what the manipulation of the lines should be but you could use:
std::string::find() and std::string::substr()
boost::split()
EDIT:
To avoid storing every line in memory at once the initial copy() can changed to write to an alternative file, followed by a file rename():
std::ifstream in("file.txt");
std::ofstream out("file.txt.tmp");
if (in.is_open() && out.open())
{
std::copy(std::istream_iterator<modified_line>(in),
std::istream_iterator<modified_line>(),
std::ostream_iterator<std::string>(out, "\n"));
// close for rename.
in.close();
out.close();
// #include <cstdio>
if (0 != std::rename("file.txt.tmp", "file.txt"))
{
// Handle failure.
}
}

You can split the task into tiny pieces and figure out how to do each in C++:
open a file as an input stream
open temporary file as an output stream
read a line from a stream
write a line to a stream
match a line to given pattern
replace text in a line
rename a file
Note: you don't need to store in memory more than one line at a time in this case.

It looks a lot like an 'INI file' syntax. You can search for it and you'll have a big load of examples. However, few of them will actually use C++ stdlib.
Here's some advices. (n.b. I assume that every lines you'll need to replace are using the syntax: <parameter> = "<value_text>")
Use the std::string::find method to locate the '=' character.
Use the std::string::substr method to split the string into different chunks.
You'll need to create a trim algorithm to remove every blank characters in front or back of a string. (It can be done with std functions)
With all that you'll then be able to split the string and isolate the parts to compare them do the needed modifications.
have fun !

Are you sure you need to do this within C++? Since you are on Unix, you can call sed which would do this easily with a command such as:
cat oldfile | sed 's/\(correct *= *\)\"TEXT\"/\1\"CORRECT_TEXT\"/' > newfile
You can call unix commands from within C++ if you have to (for example with system("command") from <cstdlib>.

Related

How do I change the value of a variable in an object with file handling?

I am working on a project with oop and file handling and I need a changeQuantity() method where the name of the item and a number(positive or negative) is passed. I want to change the quantity with this method and write the changes to the file.
My Object:
class Item(){
int itemId, quantity;
char title[25], type[10];
float price;
public:
void changeQuantity(char*, int);
};
The changeQuantity() method I am using:
void Item::changeQuantity(char* name, int quan){
fstream file;
file.open("filename.txt", ios::in | ios::out);
//after finding the object to work on
this->quantity += quan;
file.seekp(file.tellp() - sizeof(*this));
file.write((char*)this, sizeof(*this));
}
I tried with this method but it messes up the entire text file. How can I change only the quantity variable and write that change to the file without affecting anything else?????
Any kind of help would be greatly appreciated. Thank You.
PS: What I want to do here is only change the value of the quantity variable stored in the object which is stored in the txt file. The code that I am using messes the txt file.
I removed parameters except the file name from file.open() method. As fstream already has default parameters ios::in | ios::out, I removed that and it worked the way I wanted it to. But it does not work 100% of the time. It still repeats the problem sometimes and I haven't been able to find that out why.
It seems like you are mixing apples and oranges. You read something from a text file of size *this; but you read it into the binary storage of your object, and in binary mode. When it is written out, it is still in the binary format of your object. Ways to do it right:
Open the file in text mode, and read and write everything with, say gets & puts (insecure and error prone). Translate every number from text to binary when reading it in.
It is better to read them into std::string variables; as it is more powerful and less error prone. The classic C++ way to do it is e.g. the example from Input/output with files:
// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while ( getline (myfile,line) )
{
cout << line << '\n';
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
You would need to adapt it to read and translate (e.g. from text number format to a variable) each member of your object. I don't know of a way to mass read e.g. lines of text in a text file into an object's members. Once it is in binary format and properly read into your object, you can write our objects out to a binary file like that; but note: they won't be of fixed size, so you will need to write the size of the object out first, and then the object itself; and read the size of the object in and then the object itself.
In short, you are using a binary file access method, when e.g. your ints are text instead of probably 32-bit binaries, and your strings are are \n or \n\r instead of null terminated. Typical ways to handle text input and output of objects are to have one text line for each member, and translate them one at a time; or to read and write them as CSV or JSON - again one at a time for each member; and then looping through the file.
BTW: It is considered bad form to use using std; as in this example. To keep things in the std namespace from interfering with your variables and routines, it is better to use using std::string; etc.; for each thing you want to access from the std namespace.

Obtaining a certain section from a line in a file (C++)

I've spent a lot of time looking online to find a answer for this, but nothing was helping, so I figured I'd post my specific scenario. I have a .txt file (see below), and I am trying to write a routine that just finds a certain chunk of a certain line (e.g. I want to get the 5 digit number from the second column of the first line). The file opens fine and I'm able to read in the entire thing, but I just don't know how to get certain chunks from a line specifically. Any suggestions? (NOTE: These names and numbers are fictional...)
//main cpp file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
ifstream fin;
fin.open("customers.txt");
return 0;
}
//customers.txt
100007 13153 09067.50 George F. Thompson
579489 21895 00565.48 Keith Y. Graham
711366 93468 04602.64 Isabel F. Anderson
Text parsing is not such a trivial thing to implement.
If your format won't change you could try to parse it by yourself, use random access file access and use regular expressions to extract the part of the stream that you need, or read a certain quantity of chars.
If you go the regex way, you'll need C++11 or a third party library, like Boost or POCO.
If you can format the text file then you might also want to choose a standard to structure your data, like XML, and use the facilities of that format to extract the information you want. POCO might help you there.
Some simple hints in your code to help you, you will need to complete the code. But the missing pieces are easy to find at stackoverflow.
//main cpp file
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
splitLine(const char* str, vector<string> results){
// splits str and stores each value in results vector
}
int main()
{
ifstream fin;
fin.open("customers.txt");
char buffer[128];
if(fin.good()){
while(!fin.eof()){
fin.getline(buffer, 256);
cout << buffer << endl;
vector<string> results;
splitLine(buffer, results);
// now results MUST contain 4 strings, for each
// column in a line
}
}
return 0;
}
If the columns are separated by whitespace then the second column of the first row is simpy the second token extracted from the stream.
std::ifstream input{"customers.txt"}; // Open file input stream.
std::istream_iterator<int> it{input}; // Create iterator to first token.
int number = *std::next(it); // Advance to next token and dereference.

storing text to string from a file that contains spaces

So I've been doing algorithms in C++ for about 3 months now as a hobby. I've never had a problem I couldn't solve by googleing up until now. I'm trying to read from a text file that will be converted into a hash table, but when i try and capture the data from a file it ends at a space. here's the code
#include <iostream>
#include <fstream>
int main()
{
using namespace std;
ifstream file("this.hash");
file >> noskipws;
string thing;
file >> thing;
cout << thing << endl;
return 0;
}
I'm aware of the noskipws flag i just don't know how to properly implement it
When using the formatted input operator for std::string it always stops at what the stream considers to be whitespace. Using the std::locale's character classification facet std::ctype<char> you can redefine what space means. It's a bit involved, though.
If you want to read up to a specific separator, you can use std::getline(), possibly specifying the separator you are interested in, e.g.:
std::string value;
if (std::getline(in, value, ',')) { ... }
reads character until it finds a comma or the end of the file is reached and stores the characters up to the separator in value.
If you just want to read the entire file, one way to do is to use
std::ifstream in(file.c_str());
std::string all((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
I think the best tool for what you're trying to do is get, getline or read. Now those all use char buffers rather than std::strings, so need a bit more thought, but they're quite straightforward really. (edit: std::getline( file, string ), as pointed out by Dietmar Kühl, uses c++ strings rather than character buffers, so I would actually recommend that. Then you won't need to worry about maximum line lengths)
Here's an example which will loop through the entire file:
#include <iostream>
int main () {
char buffer[1024]; // line length is limited to 1023 bytes
std::ifstream file( "this.hash" );
while( file.good( ) ) {
file.getline( buffer, sizeof( buffer ) );
std::string line( buffer ); // convert to c++ string for convenience
// do something with the line
}
return 0;
}
(note that line length is limited to 1023 bytes, and if a line is longer it will be broken into 2 reads. When it's a true newline, you'll see a \n character at the end of the string)
Of course, if you a maximum length for your file in advance, you can just set the buffer accordingly and do away with the loop. If the buffer needs to be very big (more than a couple of kilobytes), you should probably use new char[size] and delete[] instead of the static array, to avoid stack overflows.
and here's a reference page: http://www.cplusplus.com/reference/fstream/ifstream/

saving files in c++ at different full paths

I am writing a program in C++ which I need to save some .txt files to different locations as per the counter variable in program what should be the code? Please help
I know how to save file using full path
ofstream f;
f.open("c:\\user\\Desktop\\**data1**\\example.txt");
f.close();
I want "c:\user\Desktop\data*[CTR]*\filedata.txt"
But here the data1,data2,data3 .... and so on have to be accessed by me and create a textfile in each so what is the code?
Counter variable "ctr" is already evaluated in my program.
You could snprintf to create a custom string. An example is this:
char filepath[100];
snprintf(filepath, 100, "c:\\user\\Desktop\\data%d\\example.txt", datanum);
Then whatever you want to do with it:
ofstream f;
f.open(filepath);
f.close();
Note: snprintf limits the maximum number of characters that can be written on your buffer (filepath). This is very useful for when the arguments of *printf are strings (that is, using %s) to avoid buffer overflow. In the case of this example, where the argument is a number (%d), it is already known that it cannot have more than 10 characters and so the resulting string's length already has an upper bound and just making the filepath buffer big enough is sufficient. That is, in this special case, sprintf could be used instead of snprintf.
You can use the standard string streams, such as:
#include <fstream>
#include <string>
#include <sstream>
using namespace std;
void f ( int data1 )
{
ostringstream path;
path << "c:\\user\\Desktop\\" << data1 << "\\example.txt";
ofstream file(path.str().c_str());
if (!file.is_open()) {
// handle error.
}
// write contents...
}

How to read and write a STL C++ string?

#include<string>
...
string in;
//How do I store a string from stdin to in?
//
//gets(in) - 16 cannot convert `std::string' to `char*' for argument `1' to
//char* gets (char*)'
//
//scanf("%s",in) also gives some weird error
Similarly, how do I write out in to stdout or to a file??
You are trying to mix C style I/O with C++ types. When using C++ you should use the std::cin and std::cout streams for console input and output.
#include <string>
#include <iostream>
...
std::string in;
std::string out("hello world");
std::cin >> in;
std::cout << out;
But when reading a string std::cin stops reading as soon as it encounters a space or new line. You may want to use std::getline to get a entire line of input from the console.
std::getline(std::cin, in);
You use the same methods with a file (when dealing with non binary data).
std::ofstream ofs("myfile.txt");
ofs << myString;
There are many way to read text from stdin into a std::string. The thing about std::strings though is that they grow as needed, which in turn means they reallocate. Internally a std::string has a pointer to a fixed-length buffer. When the buffer is full and you request to add one or more character onto it, the std::string object will create a new, larger buffer instead of the old one and move all the text to the new buffer.
All this to say that if you know the length of text you are about to read beforehand then you can improve performance by avoiding these reallocations.
#include <iostream>
#include <string>
#include <streambuf>
using namespace std;
// ...
// if you don't know the length of string ahead of time:
string in(istreambuf_iterator<char>(cin), istreambuf_iterator<char>());
// if you do know the length of string:
in.reserve(TEXT_LENGTH);
in.assign(istreambuf_iterator<char>(cin), istreambuf_iterator<char>());
// alternatively (include <algorithm> for this):
copy(istreambuf_iterator<char>(cin), istreambuf_iterator<char>(),
back_inserter(in));
All of the above will copy all text found in stdin, untill end-of-file. If you only want a single line, use std::getline():
#include <string>
#include <iostream>
// ...
string in;
while( getline(cin, in) ) {
// ...
}
If you want a single character, use std::istream::get():
#include <iostream>
// ...
char ch;
while( cin.get(ch) ) {
// ...
}
C++ strings must be read and written using >> and << operators and other C++ equivalents. However, if you want to use scanf as in C, you can always read a string the C++ way and use sscanf with it:
std::string s;
std::getline(cin, s);
sscanf(s.c_str(), "%i%i%c", ...);
The easiest way to output a string is with:
s = "string...";
cout << s;
But printf will work too:
[fixed printf]
printf("%s", s.c_str());
The method c_str() returns a pointer to a null-terminated ASCII string, which can be used by all standard C functions.