The program should begin by asking the user for an input file. This file is structured as follows:
The Name of a Person A Type of a business An Animal A Sound Old <1> Old <1> had a <2>. E-I-E-I-O And on his <2> he had a <3>, E-I-E-I-O With a <4>-<4> here and a <4>-<4> there Here a <4> there a <4>, Everywhere a <4>-<4>. Old <1> had a <2>. E-I-E-I-O
Your program should continue to prompt the user for a file name until a file has been opened.
An example dialog might be:
[bennett@mirkwood extra]$ solution Enter the name of a madlib file => bob Attempting to open bob Unable to open bob Enter the name of a madlib file => one.mad Attempting to open one.mad Success
Once the file is successfully opened, the user should be presented with each of the descriptions and allowed to provide a phrase that matches that description. The phrase should be approved by the user before the program continues. An example of this interaction might be:
Enter The Name of a Person => bob You said that bob was The Name of a Person Is this ok? (Y,N) => n Enter The Name of a Person => MacDonald You said that MacDonald was The Name of a Person Is this ok? (Y,N) => Y
Once all four phrases have been input, your program should then proceed to tell the story in the madlib. This is done by finding all instances of <n> and replacing them with the corresponding phrase. For example the line
Old <1>should become
Old MacDonaldA complete run of the program:
Enter the name of a madlib file => one.mad Attempting to open one.mad Success Enter The Name of a Person => MacDonald You said that MacDonald was The Name of a Person Is this ok? (Y,N) => y Enter A Type of a business => farm You said that farm was A Type of a business Is this ok? (Y,N) => y Enter An Animal => pig You said that pig was An Animal Is this ok? (Y,N) => y Enter A Sound => oink You said that oink was A Sound Is this ok? (Y,N) => y We will now tell a story about MacDonald, farm, pig and oink Old MacDonald Old MacDonald had a farm. E-I-E-I-O And on his farm he had a pig, E-I-E-I-O With a oink-oink here and a oink-oink there Here a oink there a oink, Everywhere a oink-oink. Old MacDonald had a farm. E-I-E-I-O
Main Algorithm: Open the file Get a Blank Filler for item 1 Get a Blank Filler for item 2 Get a Blank Filler for item 3 Get a Blank Filler for item 4 get a line while not end of file process a line get a line close the files. Open The file: Input: none Output: an open file stream while no open file Get and validate the file name Open the file Get a Blank Filler: Input: the file stream Output: a string to fill the blank with. read in the line ask the user for input while the user is not happy with result ask the user for input Process A line: Input The line and four blank fillers location = find next angle while location != npos get text to < get tag line = text past > if tag is an a get next tag
From this design, I see at least three functions
#include <iostream> #include <fstream> #include <string> using namespace std; int main() { return 0; }
When declaring a function, you first need to declare a function prototype. This is based on what the function will do. Right now we are not sure, so we will just create function prototype shells.
Between using namespace std and int main() place
using namespace std; void OpenFile(void); void GetBlank(void); void ProcessLine(void); int main() {The purpose of these prototypes is to tell the program how to use the functions. We will modify these later. This is the equivalent to declaring a variable.
Next we should define the functions. This occurs after the end of main. Again, we will just put place holders in for now. Add the following after main:
return 0; } void OpenFile(void){ cout << "In Open Files" << endl; return; } void GetBlank(void){ cout << "In Get Blank " << endl; return; } void ProcessLine(void){ cout << "In Process Line" << endl; return; }
We should put something in main to start to check our logic. The first few lines of the algorithm are fine, but the while loop is problematic for now. Change main to be
int main() { // open the file OpenFile(); // read the four blank values from the file GetBlank(); GetBlank(); GetBlank(); GetBlank(); // tell the story // get a line // while (not end of file) { ProcessLine(); // get a line // } return 0; }
This file is here and can be compiled and run.
Running this program produces
In Open Files In Get Blank In Get Blank In Get Blank In Get Blank In Process LinePerhaps not the best, but it does show that the functions are being called.
There is little that can be done in the way of testing the program until the file is open, so working on OpenFiles seems a next logical step.
We need to think about what data the function needs from the program and what data it will return. In this case, it needs no data, but will return an open file stream. We will do this by
int main() { ifstream inFile;
OpenFile(inFile);
// function prototype void OpenFile(ifstream & file); ... // function definition void OpenFile(ifstream & file){
The algorithm is fairly straight forward
done = false while not done get the name of the file from user try to open the file if successful done = true;
The code is reasonably straight forward as well. We do want some local variables, but it turns out that we can just declare them as normal.
void OpenFile(ifstream & file){ string fileName; bool fileOpen = false; while (not fileOpen) { cout << "Enter the name of a madlib file => "; cin >> fileName; cin.ignore(100,'\n'); cout << endl; cout << "Attempting to open " << fileName << endl; file.open(fileName.c_str()); if (file) { cout << "Success " << endl; fileOpen = true; } else { cout << "Unable to open " << fileName << endl; } cout << endl << endl; } return;
Finally, once we have opend the file, we should close it, so in main add a close
inFile.close(); return 0; }
This completes a function definition.
[bennett@mirkwood extra]$ step2 Enter the name of a madlib file => bob Attempting to open bob Unable to open bob Enter the name of a madlib file => one.mad Attempting to open one.mad Success In Get Blank In Get Blank In Get Blank In Get Blank In Process Line
Again not superior, but we now have the file open, so we can change the main routine to actually read the file
int main() { ifstream inFile; string line; // open the file OpenFile(inFile); // read the four blank values from the file GetBlank(); GetBlank(); GetBlank(); GetBlank(); // tell the story getline(inFile, line); while (inFile) { cout << line << endl; ProcessLine(); getline(inFile, line); } return 0; }
The code is here. If you run this code, you will see that it prints out the entire file.
read a line from the file done = false while not done ask the user for a value print the value ask the user if the value is right if yes done = true
This function needs to pass two pieces of information
The function prototype (and function header) should then become
void GetBlank(string & result, ifstream & file); ... void GetBlank(string & result, ifstream & file){
We will need space to store each of the responses so in the main routine
int main() { ifstream inFile; string blank1, blank2, blank3, blank4; string line; OpenFile(inFile); GetBlank(blank1, inFile); GetBlank(blank2, inFile); GetBlank(blank3, inFile); GetBlank(blank4, inFile);
Finally the code for GetBlank
void GetBlank(string & result, ifstream & file){ string description; char answer; bool goodAnswer = false; getline(file, description); if (file) { while (not goodAnswer) { cout << "Enter " << description << " => "; getline(cin, result); cout << "You said that " << result << " was " << description << endl; cout << "Is this ok? (Y,N) => "; cin >> answer; cin.ignore(100,'\n'); answer = toupper(answer); if (answer == 'Y') { goodAnswer = true; } } cout << endl; } return;
The code so far is contained in this file.
It might be nice to tell the user that we are about to tell them a story. I added another function, Introduction to do just that. I will pass it the four blank values, and print out an appropriate message.
Since we will not change the four blank values in the Introduction function, they will be passed by value or they will not have an & in the parameter list.
The function prototype, and function definition are
void Introduction(string lineA, string lineB, string lineC, string lineD); ... void Introduction(string lineA, string lineB, string lineC, string lineD) { cout << endl; cout << "We will now tell a story about " << lineA << ", " << lineB << ", " << lineC << " and " << lineD << endl; cout << endl; return; }
And in the main function
GetBlank(blank3, inFile); GetBlank(blank4, inFile); if (inFile) { Introduction(blank1, blank2, blank3, blank4); }
The entire code to this point is here.
void ProcessLine(string line, string a, string b, string c, string d); ... void ProcessLine(string line, string a, string b, string c, string d){
For me, this function is way too difficult to write as a single piece, so I am going to design with functions in mind. The algorithm is
while there is a tag in the line outputLine = outputLine + text before tag + text from tag line = text after tag outputLine = outputLine + line print outputLine
The code eventually becomes
void ProcessLine(string line, string a, string b, string c, string d){ size_t startPos, endPos; string outputLine; string tag; while (GetTag(line, startPos, endPos, tag)) { outputLine = outputLine + line.substr(0, startPos); line = line.substr(endPos+1, line.size()); outputLine = outputLine + FillBlank(tag, a,b,c,d); } outputLine += line; cout << outputLine << endl; return; }
Notice this uses two functions, GetTag and FillBlank.
FillBlank is the easiest of these functions to write.
The function is:
string FillBlank(string tag, string a, string b, string c, string d) { string giveBack; if(tag == "1") { giveBack = a; } else if (tag =="2") { giveBack = b; } else if (tag == "3") { giveBack = c; } else if (tag == "4") { giveBack = d; } else { giveBack = "Bad Tag"; } return giveBack; }
Finally the function GetTag It takes the line, and will return the point where the tag starts, and ends, as well as the text of the tag. In addition it will return true if a tag is found and false if a tag is not found.
bool GetTag(string line, size_t & startPos, size_t & endPos, string & tag) { size_t pos; bool returnValue = true; startPos = line.find("<") ; if (startPos == string::npos) { returnValue = false; } endPos = line.find(">", startPos); tag = line.substr(startPos+1, endPos-startPos-1); return returnValue; }The final code is here.