I have been given an assignment to create a record for each book that is listed in an inventory text file. The first line of the file has the total number of books in that file. These files have comments too. I have a function to get the next line while avoiding the comment lines. How would I implement a function that will parse the text file and read each line into a new record? I already have a "BookRecord" class with various gets and sets for the data types and I also posted what I have for a file reading function too. I can post all of the source if needed.
The text file, for example:
# Comment line
25 (the total number of books in a file)
12345 ( the classification)
A Programming Challenge (title)
613 (stock number)
23.95 (price, double)
15 (number in stock)
I have the function to skip the comment lines which is:
void Book_Inventory::GetNextLine(char *line, int lineLen)
{
int done = false;
while(!done)
{
m_InFile.getline(line, lineLen);
if(m_InFile.good()) // If a line was successfully read
{
if(strlen(line) == 0) // Skip any blank lines
continue;
elseif(line[0] == '#') // Skip any comment lines
continue;
else done = true; // Got a valid data line so return with this line
}
else // No valid line read, meaning we reached the end of file
{
strcpy(line, ""); // Copy empty string into line as sentinal value
done = true;
}
} // end while
}
bool Book_Inventory::readInventory(char *filename)
{
char line[128];
int numBooks;
int i = 0;
BookRecord *newRecord; //New BookRecord, use gets/sets for each type.
// define other variables here as needed
m_InFile.open(filename, ifstream::in);
if(!m_InFile.is_open())
{
cout << "Unable to open file" << filename << "\nProgram terminating...\n";
returnfalse;
}
// Read number of books
GetNextLine(line, 128);
numBooks = atoi(line);
while(m_InFile.good()){
m_InFile.in(line);
cout<< line <<"\n";
}
}
First, I suggest changing GetNextLine to use a std::string instead of char*. Two reasons: you don't have to specify the buffer size (128) and you avoid the danger of buffer overflow. Besides, it's just more "C++ style":
#include <string>
void Book_Inventory::GetNextLine(std::string& line)
{
for (;;) // Loop "forever", until manual break
{
m_InFile.getline(line);
if(m_InFile.good()) // If a line was successfully read
{
// See if we found a valid line
if(line.length() > 0 && line[0] != '#')
break; // Done
}
else // No valid line read, meaning we reached the end of file
{
line.clear(); // Output empty string into line as sentinal value
break; // Abort
}
} // end while
}
Next, if you don't want to create an input stream operator for the BookRecord class, a simple member function called read() will do fine:
bool BookRecord::read()
{
std::string line;
// Read classification
GetNextLine(line);
if (line.empty())
returnfalse; // Abort
<classification member variable> = atoi(line.c_str());
// Read title
GetNextLine(line);
if (line.empty())
returnfalse; // Abort
<title member variable> = line;
// ... read other values ...
returntrue; // Success
}
Then, read inventory like this:
1 2 3 4 5 6 7 8 9 10 11 12 13
vector<BookRecord> records;
std::string line;
GetNextLine(line);
if (line.empty())
returnfalse;
int numBooks = atoi(line.c_str());
for (int i = 0; i < numBooks; ++i) {
BookRecord rec;
if (!rec.read())
returnfalse; // Error
records.push_back(rec);
}
Note that you have to do the same kind of explicit error checking everywhere you call GetNextLine(), read() etc. You can avoid this by using exceptions, but maybe you don't know about exceptions yet. Please ask if you want me to demonstrate.
Thank you for your help! But is there any way to combine the 2nd and 3rd code snippet you typed? Also, I cant use the STL (this is a C++ data structures class, we are making linked lists from the ground up) and I cant create any additional functions or classes, so this must all be contained inside of readInventory(). Also, I cant change it to std::string& line because my professor wants it as a char because other functions are looking for a char.
Is this a step in the right direction:
1 2 3 4 5 6
for (int i = 0; i <numBooks; ++i) {
BookRecord rec;
if(!rec.read())
returnfalse;
(a new bookrecord)
}
I think someone should have a word with your professor...
To combine the two code snippets, just compile them together. That's what I meant when calling read() from the last snippet. Since the read() function is a member of BookRecord, you probably want to write it where you have the rest of the class, and the last snippet should be part of the inventory class.
If you're manually creating linked lists, your BookRecord class probably have a pointer to the next record. You simply link them together when creating them. You can't create a BookRecord on the stack, you must use "new BookRecord" then.
1 2 3 4 5 6 7 8 9 10 11 12
BookRecord* head = 0;
BookRecord* tail = 0;
for (int i = 0; i <numBooks; ++i) {
BookRecord * rec = new BookRecord();
if(!rec->read())
returnfalse; // Can't actually return, must delete list first
if (head == 0)
head = rec;
if (tail != 0)
tail->next = rec;
tail = rec;
}
You will have to figure out how to clean up this and make sure delete is called for every object you created. Again, using exceptions would help you a lot.
Okay so if you needed to read in values into each of the new records variables such as the classification, name and stock number. How would you do that? I know you could probably say something like line = rec->setClassification() or line = rec->setName()..but how would you make the code differenciate between new lines?
// Read number of books
GetNextLine(line, 128);
numBooks = atoi(line);
for(int i = 0; i < numBooks; i++){
BookRecord * rec = new BookRecord();
cout<<i<<"books left..";
GetNextLine(line, 128);
rec->setStockNum(long(line));
GetNextLine(line, 128);
rec->setName(line);
GetNextLine(line, 128);
rec->setClassification(int(line));
GetNextLine(line, 128);
rec->setCost(atof(line));
GetNextLine(line, 128);
rec->setNumberInStock(int(line));
GetNextLine(line, 128);
rec->printRecord();
GetNextLine(line, 128);
}
}
It does work (meaning will read a line into a new record each time) and will read in the lines correctly but the lines are off by a line or two. Here is what my input file is:
1 2 3 4 5 6 7 8 9 10 11
2
12345
CS221 A Programming Challenge
613
23.95
15
10000
A+ For Dummies
2000
3.50
1
Or, in easer to read terms :)
1 2 3 4 5 6
Number of books in file total
Stock Number
Title
Classification
Cost
Number In stock
This is what my program prints:
1 2 3 4 5 6 7 8 9 10 11
Name:CS221 A Programming Challenge
Cost:$23.95
StockNum:1244456
NumberInStock:1244456
Classification:1244456
Name:3.50
Cost:$0
StockNum:1244456
NumberInStock:1244456
Classification:1244456
Press any key to continue . . .
Am I not priming my loop correctly or something similar to that?