Parsing a text file

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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
            else if(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
}



What I have so far:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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";
        return false;
    }
    // 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":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool BookRecord::read()
{
    std::string line;

    // Read classification
    GetNextLine(line);
    if (line.empty()) 
        return false; // Abort
    <classification member variable> = atoi(line.c_str());

    // Read title
    GetNextLine(line);
    if (line.empty())
        return false; // Abort
    <title member variable> = line;

    // ... read other values ...

    return true; // 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())
    return false;
int numBooks = atoi(line.c_str());

for (int i = 0; i < numBooks; ++i) {
    BookRecord rec;
    if (!rec.read())
        return false; // 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.
Last edited on
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())
return false;
(a new bookrecord)
}
Last edited on
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())
        return false; // 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.
Last edited on
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?
No you can not read values into new records like this:
1
2
line = rec->setClassification();
line = rec->setName();


The assignment operator always updates the operand on the left hand side, line in this case, so you can't update the record like this.

You typically set values like this:
1
2
rec->setClassification(line);
rec->setName(line);


Or, though less commonly used, you can declare member functions that return by reference, and update like this:
1
2
rec->classification() = line;
rec->name() = line;


Of course, you can also have the fields public, and assign directly, but it's usually recommended to have all fields private:
1
2
rec->classification = line;
rec->name = line;
Last edited on
Okay, using the examples you gave me I have come up with this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    // 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?

Thank you for your help!
This is not how to convert a string to an integer:
 
int(line)


I don't know why you do that now, because in your original posting, you had that correct:

 
atoi(line);
I see where I made my mistake, and, yes atoi(line) is the correct way to do that.

Thank you for your help!!

For anyone interested here is the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	for(int i = 0; i < numBooks; i++){
		
		BookRecord * rec = new BookRecord();
		
		GetNextLine(line, 128);
		rec->setStockNum(atof(line));
		GetNextLine(line, 128);
		rec->setName(line);
		GetNextLine(line, 128);
		rec->setClassification(atoi(line));
		GetNextLine(line, 128);
		rec->setCost(atof(line));
		GetNextLine(line, 128);
		rec->setNumberInStock(atoi(line));
		
		// rec->printRecord(); used for testing		

	}
Topic archived. No new replies allowed.