istringstream put string back on input and read again - c++

So i read out lines out of a file and then read out the lines via stringstream.
I found the Issue that because of the format of the line rarely 2 seperate parts are written together and get read together as one string. I tryed to fix that situation by putting the wrong read value back on the stream and read again but it looks like istringstream doesnt care i put the chars back. they simply dont get read out again.
Here the Problem broken down. S1 is a good string. S2 adresses the Issue with the wrong read in the comments:
In short. Is it possible to put a string back on istringstream and read it with the next operation??
#include <sstream>
#include <string>
#include <vector>
int main()
{
std::string device_id; //126, I_VS_MainVoltageAvailabl
std::string ea_type; //E
std::string address; //0.1
std::string data_type; //BOOL
std::vector<std::string> comment; //VS - Steuerspannung vorhanden / Main voltage available"
std::string s1 = "126,I_Btn_function_stop E 1.2 BOOL Taster Stopp Funktion / Button Stop Function";
std::string s2 = "126,I_VS_MainVoltageAvailablE 0.1 BOOL VS - Steuerspannung vorhanden / Main voltage available";
std::istringstream ist{ s2 };
ist >> device_id; // Read 126, I_VS_MainVoltageAvailablE the E should be read in ea_type
ist >> ea_type; // 0.1
//my idea
if (!ea_type.empty() && isdigit(static_cast<unsigned char>(ea_type[0]))) { //first is a digit so already next was read
for (const auto& x : ea_type) //Try to put 0.1 in the stream
ist.putback(x);
ea_type = device_id[device_id.size() - 1]; // = "E"
device_id.pop_back(); // = "126, I_VS_MainVoltageAvailabl"
}
ist >> address; // Expected "0.1" instead "BOOL" why 0.1 was putback on the stream???
ist >> data_type;
for (std::string in; ist >> in;)
comment.push_back(in);
}

As usual, people are ignoring return codes. putback has a return code for a reason, and when it is false, it means putback failed.
In particular, std::istringstream is input string stream, and as such, is input-only stream. Because of that, you can't use putback on it, it will always fail.
However, you can use std::stringstream instead, and with that putback will behave the way you want it to.

I would suggest that your logic is malformed. What you actually have is a fixed field format which is not amenable to the istream extraction operators.
You would do better to read a whole line of input, then extract the "fields" by their column offsets.
Or, read one byte at a time, appending to the string variable you want to extract, until you have read enough bytes to fill it. That is, read 29 bytes into device_id, then however many (1? 8?) bytes into ea_type, etc.
I want to question your comments, though. The istream string extractor operator>>(std::istream&, std::string&) will pull one space delimited token off the input stream. In other words, your first extraction pulls off "126,". So the rest of the logic is completely wrong.

Related

A simple way to read TXT config files in C++

this question seems to be already asked but I did not find any convenient solution for my case.
I have the following TXT config file to read in C++:
--CONFIGURATION 1 BEGIN--
IP address: 192.168.1.145
Total track length [m]: 1000
Output rate [1/s]: 10
Time [s]: 1
Running mode (0=OFF 1=ON): 1
Total number of attempts: 10
Mode (0=OFF, 1=BEG, 2=ADV, 3=PROF): 1
--Available only for Administrators--
Variable 1 [mV]: 2600
Gain 1 [mV]: 200
Position tracking (0=OFF 1=ON): 0
Coefficient 2 [V]: 5.2
--CONFIGURATION 1 END--
--CONFIGURATION 2 BEGIN--
Max track distance [m]: 10000
Internal track length [m]: 100
Offset distance [mV]: 1180
GAIN bias [mV]: 200
Number of track samples: 1000
Resolution (1 or 2) [profile]: 1
--CONFIGURATION 2 END--
I need to store only the value at the end of each line that could be a string (in the case of the IP address), an int, a float or a bool inside a struct. In C there is a very simple solution, I read each single line using an expression as follows:
if(!fscanf(fp, "%*s %*s %*s %*s %d\n", &(settings->trackLength))) {
printf("Invalid formatting of configuration file. Check trackLength.\n");
return -1;
}
The %*s allows to discard the label of the line and the spaces before the interested value. I use fgets to skip the empty lines or the titles. This way works also in C++. Is it good to leave my code as is or do you see a better and simple way to do this in C++?
Thank you very much.
Also in C++ it is easy to split a line. I have already provided several answers here on SO on how to split a string. Anyway, I will explain it here in detail and for your special case. I also provide a full working example later.
We use the basic functionality of std::getline which can read a complete line or the line up to a given character. Please see here.
Let us take an example. If the text is stored in a std::string we will first put it into a std::istringstream. Then we can use std::getline to extract the data from the std::istringstream. That is always the standard approach. First, read the complete line from a file using std::getline, then, put it in a std::istringstream again, to be able extract the parts of the string again with std::getline.
If a source line looks like that:
Time [s]: 1
We can obsserve that we have several parts:
An identifier "Time [s]",
a colon, which acts as a separator,
one or more spaces and
the value "1"
So, we could write something like this:
std::string line{}; // Here we will store a complete line read from the source file
std::getline(configFileStream, line); // Read a complete line from the source file
std::istringstream iss{ line }; // Put line into a istringstream for further extraction
std::string id{}; // Here we will store the target value "id"
std::string value{}; // Here we will store the target "value"
std::getline(iss, id, ':'); // Read the ID, get read of the colon
iss >> std::ws; // Skip all white spaces
std::getline(iss, value); // Finally read the value
So, that is a lot of text. You may have heard that you can chain IO-Operations, like in std::cout << a << b << c. This works, because the << operation always returns a reference to the given stream. And the same is true for std::getline. And because it does this, we can use nested statements. Meaning, we can put the second std::getline at this parameter position (actually the first paramater) where it expects a std::istream. If we follow this approach consequently then we can write the nested statement:
std::getline(std::getline(iss, id, ':') >> std::ws, value);
Ooops, whats going on here? Let's analyze from inside out. First the operation std::getline(iss, id, ':') extracts a string from the std::istringstream and assign it to variable "id". OK, understood. Remember: std::getline, will return a reference to the given stream. So, then the above reduced statement is
std::getline(iss >> std::ws, value)
Next, iss >> std::ws will be evaluated and will result in eating up all not necessary white spaces. And guess what, it will return a refernce to the gievn stream "iss".
Statement looks now like:
std::getline(iss, value)
And this will read the value. Simple.
But, we are not finished yet. Of course std::getline will return again "iss". And in the below code, you will see something like
if (std::getline(std::getline(iss, id, ':') >> std::ws, value))
which will end up as if (iss). So, we use iss as a boolean expression? Why does this work and what does it do? It works, because the bool operator of the std::stream is overwritten and returns, if the state is OK or has a failure. Please see here for an explanation. Always check the result of any IO-operation.
And last but not least, we need to explain the if statement with initializer. You can read about it here.
I can write
if (std::string id{}, value{}; std::getline(std::getline(iss, id, ':') >> std::ws, value)) {
which is the similar to
std::string id{}, value{};
if (std::getline(std::getline(iss, id, ':') >> std::ws, value)) {
But the first example has the advantage that the defined variables will be only visible within the if-statements scope. So, we "scope" the variable as narrow as possible.
You should try to do that as often as possible. You should also always check the return state of an IO-operation by applying if to a stream-operation, as shown above.
The complete program for reading everything will then just be a few lines of code.
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <unordered_map>
#include <iomanip>
int main() {
// Open config file and check, if it coul be opened
if (std::ifstream configFileStream{ "r:\\config.txt" }; configFileStream) {
// Here we wills tore the resulting config data
std::unordered_map<std::string, std::string> configData;
// Read all lines of the source file
for (std::string line{}; std::getline(configFileStream, line); )
{
// If the line contains a colon, we treat it as valid data
if (if (line.find(':') != std::string::npos)) {
// Split data in line into an id and a value part and save it
std::istringstream iss{ line };
if (std::string id{}, value{}; std::getline(std::getline(iss, id, ':') >> std::ws, value)) {
// Add config data to our map
configData[id] = value;
}
}
}
// Some debug output
for (const auto& [id, value] : configData)
std::cout << "ID: " << std::left << std::setw(35) << id << " Value: " << value << '\n';
}
else std::cerr << "\n*** Error: Could not open config file for reading\n";
return 0;
}
For this example I store the ids and values in a map, so that they can be accessed easily.

Why does reading '\n' character from keyboard into string variable not work & how can I do that? [duplicate]

How do I also read a new line using C++ >> operator?
ifstream input("doc.txt".c_str());
vector<string> contents;
while (input >> word) {
contents.push_back(word);
}
For a file:
hello
world
C++ is the best tool
should return
hello
\n
world
\n
C++
is
the
best
tool
P/S: this is a reduced problem from a bigger one. The way I parse file lead to this problem.
You can use std::getline, and push_back the "\n" yourself, as mentioned by jaggedSpire:
std::ifstream input("doc.txt");
std::vector<std::string> contents;
for (std::string line; std::getline(input, line);) {
std::istringstream str(line);
for (std::string word; str >> word;) {
contents.push_back(word);
}
contents.push_back("\n");
}
If you're looking to specifically use operator>> and you don't technically need to use strings specifically, you can simply make a custom class with the behavior you want when it's read in from an istream. It can even be (mostly) a wrapper for a string, with custom behavior when reading initial whitespace.
class StringAndNewline{
std::string str_;
friend std::istream& operator>>(std::istream& in, StringAndNewline& str);
public:
StringAndNewline() : str_(){}
StringAndNewline(std::string str) : str_(str){}
const std::string& str() const noexcept {return str_;}
std::string release() {return std::move(str_);}
};
The string read in operator automatically ignores all preceding whitespace to a sequence of non-whitespace characters, as defined by the present locale. This is the behavior you want to change, and as it turns out it's pleasantly simple to do so.
Disposal of the initial whitespace is commonly preformed by something called a sentry object, which also checks that the stream is valid and sets the stream's failbit if it's at the end of the file. While its default behavior is to consume whitespace until it encounters a non-whitespace character, this is controlled by a flag in its constructor, so we can use that very nice encapsulated stream validity check it offers.
The string overload of operator>> makes and checks a sentry, then reads until it encounters whitespace, the end of the stream, or a read fails. We can simply ensure that its sentry never encounters whitespace by dealing with it ourselves.
Thus the ultimate read-in structure for our custom class' custom operator>> will look something like this:
make non-whitespace eating sentry
check sentry, returning the failed stream if it's invalid
deal with whitespace
read data into wrapped string
return the stream
Since we're only concerned with the '\n' characters in our whitespace that's simple too: just loop while the stream is valid (if it runs out of space before hitting either of our conditions, it sets failbit like we would want) and exit the loop if one of two conditions are net: we get a newline character, or we get a non-whitespace character. Again, pleasantly simple:
std::istream& operator>>(std::istream& in, StringAndNewline& str){
std::istream::sentry sentry{in, true}; // make a sentry that doesn't eat whitespace
if(!sentry){return in;} // check the sentry
std::locale
presentLocale{}; // get the present locale
char presentChar;
while(in.get(presentChar)){ // while the stream is valid
if(presentChar == '\n'){ // if we get a newline
str.str_ = "\\n"; // set the string to an escaped newline
break; // exit the loop
}
// if we get a non-whitespace character
else if(!std::isspace(presentChar, presentLocale)){
in.unget(); // replace the character in the stream
in >> str.str_; // take advantage of the existing string operator
break; // done with loop
}
}
return in; // return the istream, whatever state it might be in
}
Once this is done, we set up an ostream operator for ease of printing:
std::ostream& operator<<(std::ostream& out, const StringAndNewline& str){
return out << str.str();
}
and test our code:
int main (){
std::istringstream file(
"hello\n"
"world\n"
"C++ is the best tool"
);
StringAndNewline
wordOrNewline;
while(file >> wordOrNewline){
std::cout << wordOrNewline << '\n';
}
}
which prints this:
hello
\n
world
\n
C++
is
the
best
tool
just like we wanted! Live on Coliru
You could even write a string operator if you really wanted to to easily convert the wrapper class to strings, but I'll leave that to you.
Try using getline (http://www.cplusplus.com/reference/istream/istream/getline/). getline will go through each line (until it sees the new line character) and returns 0 when it reaches end of file. So after each call to getline and printing it print the \n as well. Here is an example for your problem, randFile is a random file with text in it.
1 #include <iostream>
2 #include <fstream>
3 int main(){
4
5 std::ifstream myFile("randFile", std::ifstream::in);
6 char s[BUFSIZ];
7
8 while(myFile.getline(s, BUFSIZ)){
9 std::cout << s << std::endl;
10 std::cout << "\\n"<< std::endl;
11 }
12
13 return 0;
14 }
First off , you are already passing as const char * in the constructor of the stream.
Secondly , the stream reader reads characters not space , this is how it knows when to cut to strings.
Usually we read a binary file , there is a character known by the reader that tells when we skip a line the famous \n but its differents from platforms(Win , Unix).

How can we take out the multiple integer numbers in the string array or string and assign them to different int data type?

I am new to C++ and I am reading in a text file. The content of text file is like:
$ (first line)
2 (second)
MY NAME IS (whatever sentence with 10 or below characters)(third)
12 21 (forth)
22 22 (fifth)
221 (sixth)
fly jump run (seventh)
fish animal (eighth)
So I need to read all of these and store them into different variables line by line and so far I'd manage to store them into string array line by line but how can I store the numbers like 12 21 in forth line into 2 different integer variables such as int b and int c?
and also like last two line
how can I store the fly jump run fish animal into 5 different string variables respectively?
Basically Now I am putting them into a string array line by line and trying to access them and take them out of the array and store it.
if (file.is_open()){
cout<<"Congratulations! Your file was successfully read!";
while (!file.eof()){
getline(file,line);
txt[i]=line;
i++;
}
}
Just want to store every line into variables based on their data type.
The streams support streaming the content directly into the basic data types (int, double etc.). So the istream::operator>>(int&) does the work for you.
The below small sample class demonstrates it by reading your sample file into the members -- hope that helps:
class Creature
{
public:
void read(istream& stream)
{
string line;
stream.ignore(10, '\n'); // skip line 1 (= $)
stream >> m_integers[0]; // line 2 = single int
stream.ignore(1, '\n'); // skip end of line
getline(stream, m_sentence); // get the full sentence line ..
// and the rest ... we can read that in a single code line ...
stream >> m_integers[1] >> m_integers[2] >> m_integers[3] >> m_integers[4]
>> m_integers[5] >> m_whatCanIdDo[0] >> m_whatCanIdDo[1] >> m_whatCanIdDo[2] >> m_whatIAm[0] >> m_whatIAm[1];
}
private:
string m_sentence;
int m_integers[6];
string m_whatCanIdDo[3];
string m_whatIAm[2];
};
Calling the function:
int main()
{
ifstream file;
file.open("creature.txt");
Creature cr;
cr.read(file);
file.close();
}
There are several ways of doing this, but one of the most straightforward is to use a stringstream.
To do this, copy the lines you want to tokenize from your txt array into a stringstream. Use the stream extratction operator (>>) to read out each word from that line, separated by a space, into a separate variable.
//Required headers
#include <string>
#include <sstream>
...
string word1, word2;
stringstream words(txt[lineNumber]);
words >> word1 >> word2;
//Process words
For each line you tokenize, you'll have to reset the stream.
//Read in next line
lineNumber++;
//Reset stream flags
words.clear();
//Replace the stream's input string
words.str(txt[lineNumber]);
words >> word1 >> word2;
//Process new words
You can use the same process for both integers and strings. The stream extraction operator will automatically convert strings to whatever data type you give it. However, it's up to you to make sure that the data it's trying to convert is the correct type. If you try to write a string to an int using a stringstream, the stringstream will set a fail bit and you won't get any useful output.
It's a good idea to write your input to a string, and then check whether that string is, in fact, a number, before trying to write it to an integer. But that's an entirely different topic, there are many ways to do it, and there are several other questions on this site that cover it.

Using stringstream object multiple times

I am finding it difficult to wrap my head around working of stringstream. Why the second while loop in the below code does not work? If stream object is getting emptied at the end of the first while loop is there any workaround to restore it back to initial condition?
// input is string of numbers separated by spaces (eg. "22 1 2 4")
std::string input;
std::getline(std::cin, input);
stringstream stream (input);
// print individual numbers
while (stream >> n)
{
cout << n << endl;
}
// print individual numbers again
while (stream >> n)
{
cout << n << endl;
}
stringstream is a subclass of istream, so stream >> n (std::istream::operator>>) returns a reference to istream
stream can be converted to bool (std::ios::operator bool): it converts to false when it no longer has any data (reached end-of-file)
You have finished reading stream in your first loop - it no longer has any data.
If stream object is getting emptied at the end of the first while loop is there any workaround to restore it back to initial condition?
You need to store values on your own and then reuse them - copying streams is not allowed (it doesn't make sense for them really) - Why copying stringstream is not allowed?
It's not emptied, but once you reach the end, you're stuck at the end – just like with other streams.
You need to clear the error flags (stream.clear()) and then either rewind (stream.seekg(0)) or reset the input string (stream.str(input)).
You need to create the stringstream first, in order to make multiple passes over what you have read into input. input itself is just a string not stream. #include <sstream> and then after reading input create the stringstream with:
std::stringstream stream (input);
You can then read with your first while loop, but the second while will not work because the stream position is left at the end of the stringsteam after the first while and eofbit is set.
You need call stream.seekg(0) to "rewind" the file and clear eofbit, see: std::basic_istream::seekg before the second while loop.

C++ fstream: how to know size of string when reading?

...as someone may remember, I'm still stuck on C++ strings. Ok, I can write a string to a file using a fstream as follows
outStream.write((char *) s.c_str(), s.size());
When I want to read that string, I can do
inStream.read((char *) s.c_str(), s.size());
Everything works as expected. The problem is: if I change the length of my string after writing it to a file and before reading it again, printing that string won't bring me back my original string but a shorter/longer one. So: if I have to store many strings on a file, how can I know their size when reading it back?
Thanks a lot!
You shouldn’t be using the unformatted I/O functions (read() and write()) if you just want to write ordinary human-readable string data. Generally you only use those functions when you need to read and write compact binary data, which for a beginner is probably unnecessary. You can write ordinary lines of text instead:
std::string text = "This is some test data.";
{
std::ofstream file("data.txt");
file << text << '\n';
}
Then read them back with getline():
{
std::ifstream file("data.txt");
std::string line;
std::getline(file, line);
// line == text
}
You can also use the regular formatting operator >> to read, but when applied to string, it reads tokens (nonwhitespace characters separated by whitespace), not whole lines:
{
std::ifstream file("data.txt");
std::vector<std::string> words;
std::string word;
while (file >> word) {
words.push_back(word);
}
// words == {"This", "is", "some", "test", "data."}
}
All of the formatted I/O functions automatically handle memory management for you, so there is no need to worry about the length of your strings.
Although your writing solution is more or less acceptable, your reading solution is fundamentally flawed: it uses the internal storage of your old string as a character buffer for your new string, which is very, very bad (to put it mildly).
You should switch to a formatted way of reading and writing the streams, like this:
Writing:
outStream << s;
Reading:
inStream >> s;
This way you would not need to bother determining the lengths of your strings at all.
This code is different in that it stops at whitespace characters; you can use getline if you want to stop only at \n characters.
You can write the strings and write an additional 0 (null terminator) to the file. Then it will be easy to separate strings later. Also, you might want to read and write lines
outfile << string1 << endl;
getline(infile, string2, '\n');
If you want to use unformatted I/O your only real options are to either use a fixed size or to prepend the size somehow so you know how many characters to read. Otherwise, when using formatted I/O it somewhat depends on what your strings contain: if they can contain all viable characters, you would need to implement some sort of quoting mechanism. In simple cases, where strings consist e.g. of space-free sequence, you can just use formatted I/O and be sure to write a space after each string. If your strings don't contain some character useful as a quote, it is relatively easy to process quotes:
std::istream& quote(std::istream& out) {
char c;
if (in >> c && c != '"') {
in.setstate(std::ios_base::failbit;
}
}
out << '"' << string << "'";
std::getline(in >> std::ws >> quote, string, '"');
Obviously, you might want to bundle this functionality a class.