So I am working on a model loader in my DirectX 11 program, and I ran into what I think is a unique issue. So I spent a bit of time looking for a solution to this, but failed to do so. My problem is that in my file that has the texture path and list of vertices, I want to be able to pick out certain parts, and remove some aswell. Below my example file for a texture-less triangle:
T:0$
(0, 5, 0)
(5, 0, 0)
(-5, 0, 0)
^ THIS IS OLD, LOOK AT EDIT BELOW ^
Let me explain what's happening here. First, the "T:___" is my file path to the texture. I have set this to "0" because I am not using a texture. The "$" after the "T:0" is my program's mark for the end of the file path and beginning of the vertices.
Now, here is what I need my program to do.
1. Read the file until the character "$" has been reached. Then erase the first two characters(the "T:") and the "$" if it has been added as well. Finally put the remaining text into a string called TextureData. P.S. Don't erase the "T:" from the file, just my string(The file needs to stay untouched).
2. Put the remaining text(vertices) into a temporary string called VertexData, then maybe remove the parenthesis..? I would like to know how to do this, but maybe not use it at the moment.
I hope I made myself and my issue clear enough.
Thanks in advance.
--- IMPORTANT EDIT ---
I have changed my format a little bit, I looked at a .obj file and decided that that would be easier to do. My texture and vertex file now looks like this:
T:0$
v 0 5 0
v 5 0 0
v -5 0 0
--- END OF EDIT ---
This code here is what I have as a basis:
Model loading function:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open())
{
ReadLine(1); // Function not fully working, need to improve
// Load vertices and texture into strings
TVFReader.close();
return true;
}
else
{
return false;
}
}
ReadLine function(made just to organize my code and jump to a certain line and retrieve that lines data and put into a string, the cutting and modifying of the string needs to be back in the main function):
string ReadLine(int character)
{
return lineData; // I know this doesn't work, just don't know what to return?
}
Honestly with this ReadLine function, I have no idea what I'm doing. I was just making some kind of frame to show you how I would prefer the code to be organized.
Once again, thank you.
This is how I would do it:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open()) {
stringstream buffer;
buffer << TVFReader.rdbuf();
string texture_string = buffer.str();
texture_string = texture_string.substr(2, texture_string.length());
// Removes T:
texture_string.erase(texture_string.begin() + texture_string.find("$"));
// Removes the first occurrence of $
std::cout << texture_string;
/* This will print out:
* 0
* v 0 5 0
* v 5 0 0
* v -5 0 0
*/
TVFReader.close();
return true;
}
else
{
return false;
}
}
This doesn't use a ReadLine function because it loads the entire string at once. Since I'm only manipulating that string at the beginning, I would rather have the parsing and pruning logic happen in one place.
If you must iterate line-by-line, there's already a way to do that using getline:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open()) {
string line;
while(getline(TVFReader, line))
{
// Do stuff with each line
}
TVFReader.close();
return true;
}
else
{
return false;
}
}
Note that these lines don't have end-of-line characters in them.
Edit:
As per OP's comments, here's how you would split up the parsed file into separate strings:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open()) {
string header;
getline(TVFReader, header);
header = header.substr(2, header.length());
// Removes T:
header.erase(header.begin() + header.find("$"));
// Removes the first occurrence of $
std::cout << header << std::endl;
// This should print 0, which is the string between T: and $
stringstream buffer;
string line;
while (getline(TVFReader, line))
{
line = line.substr(2, line.length());
// Removes the starting "v "
buffer << line << std::endl;
// We need the end-of-line here because its been stripped
}
string texture_string = buffer.str();
std::cout << texture_string;
/* This will print out:
* 0 5 0
* 5 0 0
* -5 0 0
*/
TVFReader.close();
return true;
}
else
{
return false;
}
}
A few notes I'd like to make here. If you're in control of the way your file is structured, you shouldn't need the $ character to indicate the end of that line. The fact that there's a newline should be indication enough of that. Additionally, since every line after that represents a vector in a matrix, I don't think the v is necessary either. Finally, the T: may also be unnecessary if your files are named appropriately, like "test.texture". That way you know that the file you're reading is a texture.
This would reduce the complexity of parsing these files, and as a bonus, reduces the storage sizes for these as well.
So far it looks like you can read your file line by line using getline and then extract necessary information: figure out "line type" by checking first character, then depending on type either get substring with path or split line into chunks and convert them to floats.
Later when/if your file format becomes more complicated you might want to write a full blown parser probably using some sort of parser generator.
Related
the professor gave told us to delete a line in a txt file without the help of another file or an array,
i tried to replace the line with backspace but it print the BS character instead
void rem()
{
fstream f("test.txt");
f.seekp(3, ios_base::beg);
f.write("\b",sizeof(char));
f.close();
}
1
2
3
4
5
i want to remove 2
1
3
4
5
after searching for few hours i found that everyone use another file or a vector or the try to replay the line with BS like me.
EDIT:
this is the correct code:
void skip_line(std::fstream& f)
{
char c;
f.get(c);
f.ignore(50, '\n');
}
void getpos(int& readpos, int& writepos)
{
std::fstream f("test.txt", std::ios::in | std::ios::binary);
skip_line(f);
writepos = f.tellg();
skip_line(f);
readpos = f.tellg();
f.close();
}
void rem()
{
int writepos, readpos, length, newSize;
std::fstream f;
getpos(readpos, writepos);
f.open("test.txt");
length = readpos - writepos;
f.seekg(readpos);
for (char c; f.get(c);)
{
f.seekg(writepos);
if (c != '\n') f.put(c);
readpos++;
writepos++;
f.seekg(readpos);
}
f.close();
//fs::path p = "test.txt"
newSize = fs::file_size("test.txt") - length;
fs::resize_file("test.txt", newSize);
}
the rusult:
befor
111111
222222
333333
444444
555555
after
111111
333333
444444
555555
Backspace will not do what you hoped for. A backspace character takes up one char just like any other character. When printed on devices capable of moving the cursor backwards, that's what'll happen. It's just a visual thing and it does not work with files.
Since you are not allowed to use another file or arrays, I'm going to assume that std::vectors and std::strings are also forbidden so I suggest shifting everything down in the file, one char at a time, to overwrite the line to be removed.
You will need a function like std::getline which is capable of reading a line from a stream into a std::string - but you do not need to store any data so we can call it skip_line. It could look like this:
std::istream& skip_line(std::istream& is) {
// read until reading fails or a newline is read:
for(char ch; is.get(ch) && ch != '\n';);
return is;
}
When you've opened the file, call skip_line until you've reached the line you want to remove. If you want to remove line 2, call skip_line 1 time. If you instead want to remove line 3, call skip_line 2 times.
The get (f.tellg()) position in the stream is now where you should start writing when you move everyting in the file back to overwrite the line to be removed. Store this position in a variable called writepos.
Call skip_line one time. The get position is now where you should start reading when moving the contents of the file. Store this position in a variable called readpos.
Calculate and store the length of the line to be removed: lenght_of_line_to_be_removed = readpos - writepos.
Now, you need to read one char at a time from the readpos position and write that char to the writepos position. It could look like this:
f.seekg(readpos); // set the _get_ position where we should read from
for(char ch; f.get(ch);) { // loop for as long as you can read a char
f.seekp(writepos); // set the _put_ position where you should write to
f.put(ch); // ...and write the char
writepos += 1; // step both positions forward
readpos += 1; // -"-
f.seekg(readpos); // set the new _get_ position
}
When the above is done, everything is "shifted down" in the file - but the size of the file will still be the same as it was before:
original: 1 2 3 4 5
after : 1 3 4 5 5
If you use C++17 or newer, you can use the standard functions std::filesystem::file_size and std::filesystem::resize_file to fix this. Remember that you stored lenght_of_line_to_be_removed above. If you use a version of C++ that does not have std::filesystem, you need to use some platform specific function. Posix systems have the truncate function that can be used for this.
I have a lack of understanding about streams. The idea is, to read a file to the ifstream and then working with it. Extract Data from the stream to a string, and discard the part which is now in a string from the stream. Is that possible? Or how to handle those problems?
The following method, is for inserting a file which is properly read by the ifstream. (its a text file, containing informations about "Lost" episodes, its an episodeguide. It works fine, for one element of the class episodes. Every time i instantiate a episode file, i want to check the stream of that file, discard the informations about one episode (its indicated by "****", then the next episode starts) and process the informations discarded in a string. If I create a new object of Episode I want to discard the next informations about the episodes after "****" to the next "****" and so on.
void Episode::read(ifstream& in) {
string contents((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
size_t episodeEndPos = contents.find("****");
if ( episodeEndPos == -1) {
in.ignore(numeric_limits<char>::max());
in.clear(), in.sync();
fullContent = contents;
}
else { // empty stream for next episode
in.ignore(episodeEndPos + 4);
fullContent = contents.substr(0, episodeEndPos);
}
// fill attributes
setNrHelper();
setTitelHelper();
setFlashbackHelper();
setDescriptionHelper();
}
I tried it with inFile >> words (to read the words, this is a way to get the words out of the stream) another way i was thinking about is, to use .ignore (to ignore an amount of characters in the stream). But that doesnt work as intended. Sorry for my bad english, hopefully its clear what i want to do.
If your goal is at each call of Read() to read the next episode and advance in the file, then the trick is to to use tellg() and seekg() to bookmark the position and update it:
void Episode::Read(ifstream& in) {
streampos pos = in.tellg(); // backup current position
string fullContent;
string contents((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
size_t episodeEndPos = contents.find("****");
if (episodeEndPos == -1) {
in.ignore(numeric_limits<char>::max());
in.clear(), in.sync();
fullContent = contents;
}
else { // empty stream for next episode
fullContent = contents.substr(0, episodeEndPos);
in.seekg(pos + streamoff(episodeEndPos + 4)); // position file at next episode
}
}
In this way, you can call several time your function, every call reading the next episode.
However, please note that your approach is not optimised. When you construct your contents string from a stream iterator, you load the full rest of the file in the memory, starting at the current position in the stream. So here you keep on reading and reading again big subparts of the file.
Edit: streamlined version adapted to your format
You just need to read the line, check if it's not a separator line and concatenate...
void Episode::Read(ifstream& in) {
string line;
string fullContent;
while (getline(in, line) && line !="****") {
fullContent += line + "\n";
}
cout << "DATENSATZ: " << fullContent << endl; // just to verify content
// fill attributes
//...
}
The code you got reads the entire stream in one go just to use some part of the read text to initialize an object. Imagining a gigantic file that is almost certainly a bad idea. The easier approach is to just read until the end marker is found. In an ideal world, the end marker is easily found. Based on comments it seems to be on a line of its own which would make it quite easy:
void Episode::read(std::istream& in) {
std::string text;
for (std::string line; in >> line && line != "****"; ) {
text += line + "\n";
}
fullContent = text;
}
If the separate isn't on a line of its own, you could use code like this instead:
void Episode::read(std::istream& in) {
std::string text;
for (std::istreambuf_iterator<char> it(in), end; it != end; ++it) {
text.push_back(*it);
if (*it == '*' && 4u <= text.size() && text.substr(text.size() - 4) == "****") {
break;
}
if (4u <= text.size() && text.substr(text.size() - 4u) == "****") {
text.resize(text.size() - 4u);
}
fullContent = text;
}
Both of these approaches would simple read the file from start to end and consume the characters to be extracted in the process, stopping as soon as reading of one record is done.
While trying to read in a simple ANSI-encoded text file in text mode (Windows), I came across some strange behaviour with seekg() and tellg(); Any time I tried to use tellg(), saved its value (as pos_type), and then seek to it later, I would always wind up further ahead in the stream than where I left off.
Eventually I did a sanity check; even if I just do this...
int main()
{
std::ifstream dataFile("myfile.txt",
std::ifstream::in);
if (dataFile.is_open() && !dataFile.fail())
{
while (dataFile.good())
{
std::string line;
dataFile.seekg(dataFile.tellg());
std::getline(dataFile, line);
}
}
}
...then eventually, further into the file, lines are half cut-off. Why exactly is this happening?
This issue is caused by libstdc++ using the difference between the current remaining buffer with lseek64 to determine the current offset.
The buffer is set using the return value of read, which for a text mode file on windows returns the number of bytes that have been put into the buffer after endline conversion (i.e. the 2 byte \r\n endline is converted to \n, windows also seems to append a spurious newline to the end of the file).
lseek64 however (which with mingw results in a call to _lseeki64) returns the current absolute file position, and once the two values are subtracted you end up with an offset that is off by 1 for each remaining newline in the text file (+1 for the extra newline).
The following code should display the issue, you can even use a file with a single character and no newlines due to the extra newline inserted by windows.
#include <iostream>
#include <fstream>
int main()
{
std::ifstream f("myfile.txt");
for (char c; f.get(c);)
std::cout << f.tellg() << ' ';
}
For a file with a single a character I get the following output
2 3
Clearly off by 1 for the first call to tellg. After the second call the file position is correct as the end has been reached after taking the extra newline into account.
Aside from opening the file in binary mode, you can circumvent the issue by disabling buffering
#include <iostream>
#include <fstream>
int main()
{
std::ifstream f;
f.rdbuf()->pubsetbuf(nullptr, 0);
f.open("myfile.txt");
for (char c; f.get(c);)
std::cout << f.tellg() << ' ';
}
but this is far from ideal.
Hopefully mingw / mingw-w64 or gcc can fix this, but first we'll need to determine who would be responsible for fixing it. I suppose the base issue is with MSs implementation of lseek which should return appropriate values according to how the file has been opened.
Thanks for this , though it's a very old post. I was stuck on this problem for more then a week. Here's some code examples on my site (the menu versions 1 and 2). Version 1 uses the solution presented here, in case anyone wants to see it .
:)
void customerOrder::deleteOrder(char* argv[]){
std::fstream newinFile,newoutFile;
newinFile.rdbuf()->pubsetbuf(nullptr, 0);
newinFile.open(argv[1],std::ios_base::in);
if(!(newinFile.is_open())){
throw "Could not open file to read customer order. ";
}
newoutFile.open("outfile.txt",std::ios_base::out);
if(!(newoutFile.is_open())){
throw "Could not open file to write customer order. ";
}
newoutFile.seekp(0,std::ios::beg);
std::string line;
int skiplinesCount = 2;
if(beginOffset != 0){
//write file from zero to beginoffset and from endoffset to eof If to delete is non-zero
//or write file from zero to beginoffset if to delete is non-zero and last record
newinFile.seekg (0,std::ios::beg);
// if primarykey < largestkey , it's a middle record
customerOrder order;
long tempOffset(0);
int largestKey = order.largestKey(argv);
if(primaryKey < largestKey) {
//stops right before "current..." next record.
while(tempOffset < beginOffset){
std::getline(newinFile,line);
newoutFile << line << std::endl;
tempOffset = newinFile.tellg();
}
newinFile.seekg(endOffset);
//skip two lines between records.
for(int i=0; i<skiplinesCount;++i) {
std::getline(newinFile,line);
}
while( std::getline(newinFile,line) ) {
newoutFile << line << std::endl;
}
} else if (primaryKey == largestKey){
//its the last record.
//write from zero to beginoffset.
while((tempOffset < beginOffset) && (std::getline(newinFile,line)) ) {
newoutFile << line << std::endl;
tempOffset = newinFile.tellg();
}
} else {
throw "Error in delete key"
}
} else {
//its the first record.
//write file from endoffset to eof
//works with endOffset - 4 (but why??)
newinFile.seekg (endOffset);
//skip two lines between records.
for(int i=0; i<skiplinesCount;++i) {
std::getline(newinFile,line);
}
while(std::getline(newinFile,line)) {
newoutFile << line << std::endl;
}
}
newoutFile.close();
newinFile.close();
}
beginOffset is a specific point in the file (beginning of each record) , and endOffset is the end of the record, calculated in another function with tellg (findFoodOrder) I did not add this as it may become very lengthy, but you can find it on my site (under: menu version 1 link) :
http://www.buildincode.com
So I have a project (I don't expect anyone to do my hw for me but I'm getting boned right now on the first of 3 data structures just so I can start my project) where I need to fill a couple maps by running through some files, my example file is set up simply where I need to extract a long as the key for a value that will be a string in the map, like so:
0 A
1 B
2 C
Where my pairs would obviously be 0 is the key to A which would be a string for this project, the issue is that my instructor also said this would be a possible format:
0 W e b 1
1 W e b 2
2 W e b 3
where 0 is the key to "W e b 1". I know that I need to divide on white space but honestly I have no clue even where to begin, I have tried a couple methods but I can only get the first character of the string in this second case.
Here is ultimately what I am sitting on, don't worry about the whole boolean return and the fact that I know the whole opening the file and checking of it should occur outside this function but my professor wants all that in this function.
bool read_index(map<long, string> &index_map, string file_name)
{
//create a file stream for the file to be read
ifstream index_file(file_name);
//if file doesn't open then return false
if(!index_file)
return false;
string line;
long n;
string token;
//read file
while(!index_file.eof())
{
getline(?)
//not sure how to handle the return from getline
}
//file read?
return !index_file.fail();
}
You could possibly use strtok() function for line splitting if you are a pure C lover, but there's a good old C++ way for splitting file data: just redirect cin stream to your file, it splits any valid separators -- whitespaces, tabs, newlines, you'll need only to keep a line counter for yourself
std::ifstream in(file_name);
std::streambuf *cinbuf = std::cin.rdbuf(); //better save old buf, if needed later
std::cin.rdbuf(in.rdbuf()); //redirect std::cin to file_name
// <something>
std::string line;
while(std::getline(std::cin, line)) //now the input is from the file
{
// do whatever you need with line here,
// just find a way to distinguish key from value
// or some other logic
}
I have a file which holds protein coordinates as well as other information preceding it. My aim is to look for a certain line called "$PARAMETERS" and then copy from there every line succeeding it till the end of the file.
How can I get that done? This is the small code I wrote part of the entire program (that someone else wrote years ago, and I took over to upgrade his code for my research):
ifstream InFile;
InFile.open (DC_InFile.c_str(), ios::in);
while ( not InFile.eof() )
{
Line = NextLine (&InFile);
if (Line.find ("#") == 0) continue; // skip lines starting with # (comments)
if (Line.length() == 0) continue; // skip empty lines
size_t pos = Line.find("$PARAMETERS");
Line.copy(Line.begin("$PARAMETERS")+pos, Line.end("$END"));
&Line.copy >> x_1 >> y_2 >> z_3;
}
Bearing in mind that I defined Line as string
I guess you need to read data between $PARAMETERS and $END, not from $PARAMETERS until end of file. If so, you can use the following code:
string str;
while (getline(InFile, str))
{
if (str.find("#") == 0)
continue;
if (str.length() == 0)
continue;
if (str.find("$PARAMETERS") == 0)
{
double x_1, y_2, z_3; // you want to read numbers, i guess
while (getline(InFile, str))
{
if (str.find("$END") == 0)
break;
stringstream stream(str);
if (stream >> x_1 >> y_2 >> z_3)
{
// Do whatever you want with x_1, y_2 and z_3
}
}
}
}
This will handle multiple sections of data; not sure if you really want this behavior.
For example:
# comment
$PARAMETERS
1 2 3
4 5 6
$END
#unrelated data
100 200 300
$PARAMETERS
7 8 9
10 11 12
$END
I'm not sure what you want on the first line of the copied file but assuming you get that straight and you haven't read beyond the current line, you can copy the tail of the fike you are reading like this:
out << InFile.rdbuf();
Here out is the std::ostream you want to send the data to.
Note, that you should not use InFile.eof() to determine whether there is more data! Instead, you should read what you want to read and then check that the read was successful. You need to check after reading because the stream cannot know what you are trying to read before you have done so.
Following up on Dietmar's answer: it sounds to me like you
should be using std::getline until you find a line which
matches your pattern. If you want that line as part of your
output, then output it, then use Dietmar's solution to copy the
rest of the file. Something like:
while ( std::getline( in, line ) && ! isStartLine( line ) ) {
}
if ( in ) { // Since you might not have found the line
out << line << '\n'; // If you want the matching line
// You can also edit it here.
out << in.rdbuf();
}
And don't put all sorts of complicated parsing information,
with continue and break, in the loop. The results are both
unreadable and unmaintainable. Factor it out into a simple
function, as above: you'll also have a better chance of getting
it right. (In your case, should you match "$PARAMETERS #
xxx", or not?) In a separate function, it's much easier to get
it right.