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.
Related
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.
Lets say I have a code :
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
string line,wantedString,newString;
fstream subor("test.txt");
while (!subor.fail()) // read line from test.txt
{
getline(subor,line);
line.substr(line.find("?")+1);
wantedString=line.substr(line.find("?")+1); // will take everything after '?' character till
'\n'
}
cout<<"Enter new text to replace wantedString :";
getline(cin,newString);
// how to use string::replace() please ?
/I tried this but does not work
getline(subor,line);
line.replace(wantedString,string::npos,newString);
return 0;
}
In test.txt is written only one line :
something?replace
note: there is no '\n' in the file
error thrown by compiler is :
error: no matching function for call to 'std::__cxx11::basic_string<char>::replace(std::__cxx11::string&, const size_type&, std::__cxx11::string&)'
can you please answer working code with commented explaining why is it like you did it ?
I have studied string::replace() method here:
http://www.cplusplus.com/reference/string/string/replace/
Is my logic of using string::find() as a starting point for string to be replaced ?
Is my logic of using string::find() as a starting point for string to be replaced ?
Yes, but then you threw away the iterator/index result of find and went to get the substring instead.
Replace doesn't take a string.
It takes an iterator/index.
So just pass what you got from find, into replace. (Be careful of edge cases! Check for errors! Read the documentation for both functions.)
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
fstream subor("test.txt");
string line;
while (getline(subor,line))
{
// Find the index of the character after the first '?'
const size_t wantedStringPos = line.find("?")+1;
// Prompt for a replacement string
cout << "Enter new text to replace wantedString: ";
string newString;
getline(cin,newString);
// Perform the replacement
line.replace(wantedStringPos, string::npos, newString);
// Now do something with `line`
// [TODO]
}
}
(I've also fixed an off-by-one error in your loop.)
You then need to actually write the new, modified string back to the file: the file doesn't automatically get updated in sync with the copy of the data you previously read out from it.
I am reading an XML file into a stringstream buffer in order to parse it using RapidXML. RapidXML is only parsing the names of the XML nodes, but none of their attribute names or values. After some experimentation, I discovered that the problem is not likely to be with RapidXML, but with conversion of the stringstream buffer to a string using std::string content(buffer.str());. The '=' characters that are so important to XML parsing are converted to ' ' (space characters), prior to any RapidXML processing.
The character replacement is evident in the console window when the cout << calls are made in the code below, which is before RapidXML gets its hands on the string.
My code is as follows:
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <conio.h>
#include <string>
#include <stdlib.h>
#include <rapidxml.hpp>
#include <vector>
#include <sstream>
using namespace std;
using namespace rapidxml;
//... main() and so forth, all works fine...
ifstream file(names.at(i)); // names.at(i) works fine...
//...
file.read(fileData, fileSize); // works fine...
//...
// Create XML document object using RapidXML:
xml_document<> doc;
//...
std::stringstream buffer;
buffer << file.rdbuf();
// This is where everything looks okay (i.e., '=' shows up properly):
cout << "\n" << buffer.str() << "\n\nPress a key to continue...";
getchar();
file.close();
std::string content(buffer.str());
// This is where the '=' are replaced by ' ' (space characters):
cout << "\n" << content << "\n\nPress a key to continue...";
getchar();
// Parse XML:
doc.parse<0>(&content[0]);
// Presumably the lack of '=' is preventing RapidXML from parsing attribute
// names and values, which always follow '='...
Thanks in advance for your help.
p.s. I followed advice on using this technique for reading an entire XML file into a stringstream, converting it to a string, and then feeding the string to RapidXML from the following links (thanks to contributors of these pieces of advice, sorry I can't make them work yet...):
Automation Software's RapidXML mini-tutorial
...this method was seen many other places, I won't list them here. Seems sensible enough. My errors seem to be unique. Could this be an ASCII vs. UNICODE issue?
I also tried code from here:
Thomas Whitton's example converting a string buffer to a dynamic cstring
code snippet from the above:
// string to dynamic cstring
std::vector<char> stringCopy(xml.length(), '\0');
std::copy(xml.begin(), xml.end(), stringCopy.begin());
char *cstr = &stringCopy[0];
rapidxml::xml_document<> parsedFromFile;
parsedFromFile.parse<0>(cstr);
...with similar RapidXML failure to parse node attribute names and values. Note that I didn't dump the character vector stringCopy to the console to inspect it, but I am getting the same problem, which for review is:
I am seeing correctly parsed names of XML tags after RapidXML parsing of the string fed to it for analysis.
There are no correctly parsed tag attribute names or values. These are dependent upon the '=' character showing up in the string to be parsed.
If you look closely the = characters probably aren't being replaced by spaces, but zero bytes. If you look at the rapidxml documentation here:
http://rapidxml.sourceforge.net/manual.html#namespacerapidxml_1differences
It specifically states that it modifies the source text. This way it can avoid allocating any new strings, instead it uses pointers to the original source.
This part seems to work correctly, maybe the problem is with the rest of your code that's trying to read the attributes?
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>.
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...
}