// basic file operations
#include <iostream>
#include <fstream>
#include <string>
using namespace std;



int main ()
{
  	
	char*  pch;
	string line;
	fstream myfile;
	myfile.open ("Ahoo.txt");

  		if (myfile.is_open())
 		{
			while (!myfile.eof())
  			{
				getline (myfile,line);
				cout<<line<<endl;
					
					

				pch = strtok (line," ,.-");
  					
				while (pch != NULL)
  				{
   					 cout<<pch;
					 pch = strtok (NULL, " ,.-");
  				}
			}
		
			myfile.close();
				
		}

		else 
		{
			cout<<"File not opened";
		}
  return 0;
}
I want to create program that read text from a file, copies it to a string and then break it into tokens. This is my first post kindly help!

Don't use strtok(). It is for handling C-strings (not std::strings), and it modifies the string.

Use the find_first_of() and substr() string member functions instead.

Hope this helps.

http://www.cppreference.com/wiki/string/start

That's a reference for the member methods of the STL string class including find_first_of() and subst(). You would probably want to use those two methods in combination to separate out tokens based on the criteria you have.

An alternate would be to change the STL string into a C style string and continue to use strtok() as you have. The c_str() member method of the STL sting class can help you do that.

An alternate would be to change the STL string into a C style string and continue to use strtok() as you have. The c_str() member method of the STL sting class can help you do that.

You might want to mention that c_str() returns a pointer to a non-modifiable string, so that you would actually have to make a copy of the std::string contents in order to avoid undefined behavior when using strtok.

Thanks for the help, Now i want the user to enter a word, then the program will search it in the file , if it is in the file, it tells the word is found, else not,
I m having problem with the string compare.
here is the code:

// basic file operations
#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
#include <string.h>

using namespace std;



int main ()
{   
  	char * cstr;
	char * p;
	string 			  line;
	fstream 		  myfile;
	char delim[]	=" :,.-;!?";
	char arr[10];
	
	
	cout<<"Hello , Enter a word to search in a text file.";
	cin>>arr;
	
		
	myfile.open ("Ahoo.txt");
	if (myfile.is_open())
		{
			
		while (!myfile.eof())
		{
					
		getline (myfile,line);
		cstr = new char [line.size()+1];	
		strcpy (cstr, line.c_str());											
		p=strtok (cstr,delim);													
 							
			while (p!=NULL)
				 {
		/*cout << p << endl;*/
		p=strtok(NULL,delim);
    				
		for (int i=0; i<10; i++)
		{	
		for (int k=0; k<10; k++)
		{
		if 	(strcmp(arr[i],p[k])==0)
		{
		cout<<"FOUND";
		}
			else
		{
		cout<<"INVALID";
		}										
		}
		}
								
			}	

 		 delete[] cstr;  
				
			}
			myfile.close();
		}

	else 
		{
			cout<<"File not opened";
		}
  return 0;
}

Edited 6 Years Ago by FatimaRizwan: n/a

arr and p are strings, whereas arr and p[j] are single characters. strcmp() takes C style strings as arguments, not single characters.

In the code you have you can compare arr to each p as it is generated to see if they match. If you want to do that I would use a flag variable to keep track of whether arr is found in the file or not. On line 20 declare a variable something like this:

bool found = false;

and between line 49 and 50 add a line like this;

found = true;

Then, delete lnes 51-54 and after the while loop is completed check the value of found, outputting the invalid statement if found is false .

if(!found)
cout << "invalid";

As an unrelated tip, don't use the return value of eof() to control whether the loop will run or not. It is an error waiting to happen. Instead, change this:

while (!myfile.eof())
{ getline (myfile,line);

to this;

while (getline (myfile,line)) {

The reason has to do with how eof() works, though I find it hard to describe the problem and have yet to find a clear explanation to use as reference. The explanation is something to the effect that eof() returns true when you've tried to read beyond EOF, not when you find EOF.

compare your program to this( not compiled )

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main()
{
   ifstream file("Ahoo.txt");

   if(!file) return -1; 

   string wordToFind;
   cout <<" Enter a word to find in the file Ahoo.txt\n";
   cin >> wordToFind;

   string word;
   bool isFound = false;
   while( file >> word ){
    if( word == wordToFind ){
        isFound = true;
        break;
      }
   }

  if(isFound ) cout << "Word is found\n";
  else cout <<"Word is not found\n";

  return 0;
}

Sigh. Why is it that C++ questions always degenerate into C questions?

@firstPerson
Your code does not satisfy the OP's requirements. If the OP searches for the word "Hello" in the string "Hello, world!", your code cannot find it because it only delimits on whitespace (which would get the string "Hello," from the file, not "Hello").

You can recover somewhat from this by "trimming" the unwanted characters from the ends of every word you find, but it will not help when you read "one,two,three,four" from the file and wish to find "four".

@FatimaRizwan
If that is not a concern, then firstPerson's solution, plus the "trim" function, ought to be enough to get you going.

If it is a problem, keep reading.

But first, if you want to use C++, please get rid of the C stuff. It will only hurt you later when you fail to learn to do things the right way now.

Granted, this is probably a silly homework assignment, and what follows may be fully ignored. Well, at least I gave it something.


Tokenizing
Even though this is a fairly old concept it is still very much needed and used.

The way that strtok() does it is, shall we say, simplistic. However, we will continue with that model in mind. (Also, I will keep things simple by sticking with the std::string and std::istream data types.)

First, in order to get substrings from a string, you must know where to break it. Hence my admonition to use find_first_of() and substr() (and I should have said find_first_not_of() also, but if you had looked up the first you'll have found this one also).

There are essentially three things you need: the "source" string to tokenize, the current "index" into that string, and the last "token" you got from it. The strtok() function does this by remembering the the address of the source, the current index, and returing the address of the token in the source. This is bad, because it must modify the source to do that (which also implies that the source must be persistent), and because it uses a hidden global variable to remember where it is at (which means that you cannot use strtok() on more than one string at a time).

In C++, you can encapsulate the correct behaviors in a little class that does the same sort of things, but without goobering the source or suffering from the 'one at a time' problem.

#include <iostream>
#include <string>

//----------------------------------------------------------------------------
struct stringtokenizer
  {
  std::string            source;  // our COPY of the source
  std::string::size_type index;   // where we are at in our copy of the source
  std::string            token;   // a COPY of the last token found in our copy of the source

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // constructor
  explicit stringtokenizer( const std::string& source ):
    source( source ),
    index ( 0      )
    { }

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // copy constructor
  stringtokenizer( const stringtokenizer& stok ):
    source( stok.source ),
    index ( stok.index  )
    { }

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Extract the next token.
  // Returns whether or not it was successful.
  bool tokenize( const std::string& delimiters )
    {
    std::string::size_type index0;

    // Find the beginning of the next token, if any
    if (index == std::string::npos) return false;
    index0 = source.find_first_not_of( delimiters, index );

    // Find the end of the token, if any
    if (index0 == std::string::npos) return false;
    index = source.find_first_of( delimiters, index0 );

    // Save the result
    std::string::size_type length
      = (index == std::string::npos)
      ? std::string::npos
      : (index - index0);
    token = source.substr( index0, length );

    return true;
    }
  };

//----------------------------------------------------------------------------
int main()
  {
  using namespace std;

  unsigned n = 0;
  stringtokenizer tok( "When I greet the computer, I'll always say, \"Hello, world!\"" );
  while (tok.tokenize( ", !" ))
    {
    cout << ++n << ": " << tok.token << endl;
    }

  return 0;
  }

Notice how this behaves exactly like C's strtok(), but without the bad side-effects.

There are many other ways to do this kind of thing. In C++, it is often handy to tokenize off of an input stream directly by overriding the input stream extraction operator (the >> operator). Again, the code may be a bit longer because it takes a little more to set things up, but the result is superior in elegance, safety, and utility/ease of use. Here's a simple type that does the same as above easily enough:

#include <iostream>
#include <string>

//----------------------------------------------------------------------------
// token_t
//   This is our token data, encapsulated in a class for us.
//   It tracks the last token read and the current token delimiters.
// 
class token_t: public std::string
  {
  public:
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Standard constructor.
    explicit token_t( const std::string& delimiters = std::string() ):
      f_delimiters( delimiters )
      { }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Standard copy constructor.
    token_t( const token_t& token ):
      f_delimiters( token.f_delimiters ),
      f_token     ( token.f_token      )
      { }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Modify the token delimiter characters.
    token_t& delimiters( const std::string& delimiters )
      {
      f_delimiters = delimiters;
      return *this;
      }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Get the current token delimiter characters.
    const std::string& delimiters() const
      {
      return f_delimiters;
      }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Get the last-read token.
    const std::string& str() const
      {
      return f_token;
      }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // The istream extraction operator is what does all the work.
    friend std::istream& operator >> ( std::istream&, token_t& );

  private:
    std::string f_delimiters;
    std::string f_token;
  };

//----------------------------------------------------------------------------
// Read the next token from the input stream.
std::istream& operator >> ( std::istream& ins, token_t& token )
  {
  int c;

  // Find the beginning of the next token, if any
  if (!ins) return ins;
  while (token.f_delimiters.find( ins.peek() ) != std::string::npos)
    if (ins.get() == std::istream::traits_type::eof())
      break;

  // Find then end of the token, if any
  if (!ins) return ins;
  token.f_token.clear();  // (but first erase the previous token) 
  while (token.f_delimiters.find( c = ins.peek() ) == std::string::npos)
    {
    if (c == std::istream::traits_type::eof()) break;
    token.f_token.push_back( ins.get() );
    }

  // (The extraction operator must return its istream argument) 
  return ins;
  }

//----------------------------------------------------------------------------
#include <sstream>
using namespace std;

//----------------------------------------------------------------------------
int main()
  {
  istringstream ss(
    "When I greet the computer, I'll always say, \"Hello, world!\""
    );
  token_t       token;
  unsigned      n = 0;

  while (ss >> token.delimiters( ", !" ))
    {
    cout << ++n << ": " << token.str() << endl;
    }

  return 0;
  }

Again, there are many ways to do this kind of thing. These two examples are deliberately left simple; much more advanced tokenizing is easily possible in C++.

Well, it is time for me to get off my high horse.

Hope this helps.

Comments
Very helpful code !!
Above and beyond!

>> Your code does not satisfy the OP's requirements. If the OP searches for the word "Hello" in the string "Hello, world!", your code cannot find it because it only delimits on whitespace (which would get the string "Hello," from the file, not "Hello").

Oh, I guess I should have read the exact specification.

Also in both of your examples you provide a copy constructor, how
come? There is no need for that. And if one provides a copy constructor
one should also provide a destructor and an assignment operator,
but your example does not use dynamic allocation explicitly so there
is no need for copy ctor, unless I missed something :)

Hmm, just a good habit I've gotten into. (You just never know when a copy of your object will be used when a reference would have done.)

Stay away from always/never rules. The "Rule of Three" isn't always the right thing to do. In any case, I meant to keep the examples as simple as possible.

#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <time.h>

using namespace std;

class FileManipulator
{	
		string word;
		string name;

	public:

		void wordinput()
		{
			cout<<"Enter word:";   	cin>>word;
		}

		void nameinput()
		{
			cout<<"Enter file:";	cin>>name;
		}

		string getname()
		{
			return name;
		}

		void manipulation()
		{
			fstream  my_file;		string line;
			int found;				string ext=".txt";
			

			my_file.open (getname()+ext);
			{
				if (my_file.is_open())
			
				{
				
					while (!my_file.eof())
					{
		
						getline (my_file,line);
						found=line.find(word); 
							
						if (found != string::npos)
						{	
							cout<<found;
						}
					
						
					}
					my_file.close();
				}


				else 
				{
					cout<<"File not opened";
				}
			}		
		}

		void printFile()
		{	
			//I want to print the attributes of the file,
			/*only if the required word is found in it*/
			
		}

};

int main()
{
	FileManipulator f;
	f.wordinput();
	f.nameinput();
	f.manipulation();
	f.printFile();
	return 0;
}

Please help me with the higlighted part of the code, plus if the word is found in the file , i want to print the attributes of that file ,I dun know how to print the attributes of the file, if anybody can help me out??

Just a couple of changes:

my_file.open ((getname()+ext).c_str());  // need a c-string for this
		//you can delete { that was on this line and its match	
	            if (my_file.is_open())  //this is ok, included just for context			
		   {				
			while (getline (my_file,line))
			{  //see post #7 where Lerner explained

With those changes it worked just fine for me. Output a space after found in your highlighted code to see the indexes.

As far as the file attributes go, which do you need? You have included io.h which you can use but the caveat is that most of those functions are non-portable. You can find a reference for it with your compiler docs or there seem to be a few out there on the web (make sure it matches your implementation).

Edited 6 Years Ago by jonsca: n/a

Output a space after found in your highlighted code to see the indexes.

I dint get LANSCA, what do you mean by the above statement , If find() doesn't find what I want, then found == string::npos
else it outputs an integral value.what does that integral value meanns??

and if the specified word is found then i want to display the attributes of that text file , like its name , size , directory etc.

If find() doesn't find what I want, then found == string::npos else it outputs an integral value.what does that integral value meanns??

http://www.cplusplus.com/reference/string/string/find/
It's the index of the first occurrence of the string stored in word within your line of text that you have read in. So a multiple line file will yield a value for found for each line. However, find is able to locate your search word within another word of the text, e.g., if you are looking for "the" and the word "anthem" is in your text.

if the specified word is found then i want to display the attributes of that text file , like its name , size , directory etc.

Well filename is quite easy as you already have it from the user. The other information you'll have to get in an OS dependent way. I am assuming you are using windows but there are functions in *nix to accomplish the same thing. Search around the forums for some pointers, but you can look up the documentation for io.h or take a look at this for the specific Win32 functions for file handling (you'll have to include windows.h and make sure to designate your project as a Win32 Console app to make sure you have all the appropriate libraries).

#include<io.h>
#include<fstream>
#include<iostream>
#include<conio.h>
#include<string.h>
//#include"string";
#include <string>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>
#include <cerrno>

using namespace std;
	
class file
{
	protected:
		struct _finddata_t c_file;
		long hfile;
		string str[20];
		string* str1;
	public:
	
		
		string* getFname(string path)
		{
			string t=(path+"*.txt");
			if((hfile=_findfirst(t.c_str(),&c_file))==-1)
			{}
			
			else
			{
                int position=0;
				str[0]=path+c_file.name;
				position++;
			
				while(_findnext(hfile,&c_file)==0);
				{				
					str[position]=path+c_file.name;
					position++;
					
				}
			}

			return str;
		}

		
};


class FileManipulator 
{	
		char word[10];
		string name;
		file f;

	public:

		void word_input()	
	{	cout<<"Enter word:";  	 	cin>>word;	}

		bool manipulation1(string path)	
		{	
			fstream  my_file;
			string line;
			char * cstr;
			char	* p;
			char delim[]=" :'.,?><_+{}[]();";
                	string ext=".txt";
			bool found=false;
			string strArray[20];
			
	string* d=	(f.getFname(path));
	
		for (int i =0; i< 3; i++)
		{
		strArray[i]=d[i];
		//cout<<strArray[i]<<endl;
		}
		
		for (int j=0 ; j<3; j++)
		{
		
		my_file.open (strArray[j].c_str());
		{
		if (my_file.is_open())
		{
							
		while ( getline(my_file,line) )
		{	
								
		cstr=new char [line.size()+1];
		strcpy(cstr,line.c_str());
		p=strtok(cstr,delim);
						
		while (p!=NULL)
			{
			if (strcmp(word,p)==0)
			{
			cout << "found";
			found =true;
			return found;
			}
								
		p=strtok(NULL,delim);
			}
								

								
			if (!found)
			{			
			cout<<"Invalid";
			return found;
							
			}
			}
				
			delete[] cstr;
			my_file.close();
			}
			
			else 	{ cout<<"File not opened";	}
		}		
		
		}
		}	
		
		int manipulation2(string path)	
		{	fstream  my_file;
			string line;
			string::size_type found;
 	                string ext=".txt";
			string strArray[20];
			
			string* d=	(f.getFname(path));
			
			for (int i =0; i< 3; i++)
			{
			strArray[i]=d[i];
			//cout<<strArray[i]<<endl;
			}
		
		for (int j=0 ; j<3; j++)
		{
		my_file.open (strArray[j].c_str());
		{
		if (my_file.is_open())
		{
						
		while ( getline(my_file,line) )
		{	
		found=line.find(word); 
		if (found != string::npos){		return found;			 }
		/*if (found == string::npos){	cout<<"NOT found"<<endl; }*/
		}
		my_file.close();
		   }
		else 	{ cout<<"File not opened\n";	}
		}		
		}
		}

		
		int printFile(string path)
		{	
		string strArray[20];
		struct stat fileInfo;	string ext=".txt";
		cout << "Type	Name	Size		Created		"<<endl;
		cout << "---	---	----		-------		\n";
					
		for (int k =0 ; k<3; k++)
		{

		if (stat((strArray[k].c_str()), &fileInfo) != 0)				// Use stat( ) to get the info
		{ 
		std::cerr << "Error: " << strerror(errno) << '\n';
		return(EXIT_FAILURE);
		}
				
   		if ((fileInfo.st_mode & S_IFMT) == S_IFDIR) 
		{ 	
		cout << "Directory\t";	
		}										// From sys/types.h
					
		else
		{ 	
		cout << "File\t";		 
		}
		cout << (strArray[k].c_str())<<"\t";   			 //__fname;
		cout << fileInfo.st_size << " Bytes\t";      	// Size in bytes   					    cout << ctime(&fileInfo.st_ctime);    		 	// Creation time
   					//cout << std::ctime(&fileInfo.st_mtime);		// Last mod time
					}	
				}


};

int main(int argc, char** argv)
{	
	int num;
	string driName;
    string fName;
	string ext="/";
	string path;
	int choice;
	FileManipulator f;
	
	cout<<"WELCOME TO TXT FILE SEARCHER\n";
	cout<<"Enter a word to search\n";
	f.word_input();
	
	
	cout<<"Press number for required drive\n";
	//cout<<"0:---------------- PARENT FOLDER\n";
	cout<<"1:---------------- DRIVE C\n";
	cout<<"2:---------------- DRIVE D\n";
	cout<<"3:---------------- DRIVE E\n";
	cout<<"4:---------------- DRIVE F\n";
	cin>>choice;
	{
		/*if (choice == 0)	{   driName="	  ";	}*/
		if (choice == 1)	{	driName="C:/\n"; 	}
		if (choice == 2)	{	driName="D:/\n";	}	
		if (choice == 3)	{	driName="E:/\n";	}
		if (choice == 4)	{	driName="F:/\n";	}
	}
	cout<<"Enter Folder name:";
	cin>>fName;
	
	path=driName+fName+ext;
	
	cout << "Press 1 for exact search of word:\n";
	{
		cin>>num;
	
		if (num == 1)
		{
			bool resul=f.manipulation1(path);
			
			if (resul==true)
			{	f.printFile(path);	}
			
			else
			{	cout<<"WORD NOT FOUND\n";	}
		}

		else
		{
			int result=f.manipulation2(path);
			
			if (result)
			{	f.printFile(path);	}
			else
			{	cout<<"WORD NOT FOUND\n";	}
		}
	}
return 0;
}

There is some error with the manipulation1 and manipulation2 function please help

Edited 6 Years Ago by FatimaRizwan: n/a

What's the problem you have been having with it?

In your main() you never explicitly ask for the filename, you just tack the extension onto the folder name.

Edited 6 Years Ago by jonsca: n/a

whatever input , i give, it says word not found, file not opened!!

See my edit above if it hadn't come through.

My honest opinion, I would cut and paste all that elaborate menu stuff out for the time being.

See my edit above if it hadn't come through.

My honest opinion, I would cut and paste all that elaborate menu stuff out for the time being.

The program only asks the user to input the word which he wants to search in specific directory and folder ,then my program searches all the files in that folder, and check whether the word is found or not.
So its the responsibility of my prog to search for the file names.

The program only asks the user to input the word which he wants to search in specific directory and folder ,then my program searches all the files in that folder, and check whether the word is found or not.
So its the responsibility of my prog to search for the file names.

Have you tested your File class in isolation, that is does it (by itself) produce a list of files from a given directory?

Have you tested your File class in isolation, that is does it (by itself) produce a list of files from a given directory?

yahh , it works.

Got part of it:
In your file class

void getFname(string path,string str[]) 
         {           //change to returning an array
		 //by reference, just to make things easier

                     //leave the rest the same
                      //but instead of your while
                   do{				
			str[position]=path+c_file.name;
			position++;
			}while(_findnext(hfile,&c_file)==0);
           //and get rid of the return statement

Your while loop was only executing once after the initial time, I think it needs the prior result as a seed for the next (I'm sure there's more to it than that but I am not really familiar enough with it to say).

So test that on a few directories and try to reintegrate it with your other code.

yahh , it works.

I don't believe that for a second after I spent all that time looking over it for you! (to be polite) It was getting the first and last items of the directory and nothing more.

Edited 6 Years Ago by jonsca: n/a

Got part of it:
In your file class

void getFname(string path,string str[]) 
         {           //change to returning an array
		 //by reference, just to make things easier

                     //leave the rest the same
                      //but instead of your while
                   do{				
			str[position]=path+c_file.name;
			position++;
			}while(_findnext(hfile,&c_file)==0);
           //and get rid of the return statement

Your while loop was only executing once after the initial time, I think it needs the prior result as a seed for the next (I'm sure there's more to it than that but I am not really familiar enough with it to say).

So test that on a few directories and try to reintegrate it with your other code.


I don't believe that for a second after I spent all that time looking over it for you! (to be polite) It was getting the first and last items of the directory and nothing more.

void getFname(string path,string str[])

whats string str[] in parameters?

I was telling you to send in the array (by pointer, I said by reference by mistake) rather than returning a pointer like you did. So declare your array of strings (to store the filenames) in your fileManipulation class rather than in the file class, e.g.,

string fnames[20]; //I'd do more than that if you have big directories
getFname(path,fnames);

-then-
you can access as fnames[0], etc.

I m sorry , i am not getting you,
where to make the changes

void getFname(string path,string str[]) 
		{
			string t=(path+"*.txt");
			if((hfile=_findfirst(t.c_str(),&c_file))==-1)
			{}
			
			else
			{
                int position=0;
				str[0]=path+c_file.name;
				position++;
			
				 do{				
					str[position]=path+c_file.name;
					position++;
					}
				 while(_findnext(hfile,&c_file)==0);
			}
		
		}

Now what?

bool manipulation1(string path)	
		{	
			fstream  my_file;
			string line;
			char * cstr;
			char	* p;
			char delim[]=" :'.,?><_+{}[]();";
                	string ext=".txt";
			bool found=false;
			string strArray[20]; //keep this to use it 
			
	                      // string* d= get rid of this	
             f.getFname(path,strArray);

                            //now skip all the strArray[i] = D[i] stuff but this time
                           //you'll have the correct filenames

And more importantly you know that everything up until that point is working. Now you can make the similar changes in manipulation2. Then we can make sure that everything's working after that point in that method. Debugging, it's a wonderful thing.

EDIT: Now that I'm looking at it, it might be advantageous to either return or send in as a reference parameter the "position" variable, so you can use that to control your loops.

Edited 6 Years Ago by jonsca: n/a

I'm going to be shipping out so wrestle with that a bit more on your own. In my further testing there is some issue with the path as it is coming in from main() (it seems to have extra spaces or an extra \n in there somewhere).

It's coming along. I think you'll be proud of the end result. Happy debugging.

I'm going to be shipping out so wrestle with that a bit more on your own. In my further testing there is some issue with the path as it is coming in from main() (it seems to have extra spaces or an extra \n in there somewhere).

It's coming along. I think you'll be proud of the end result. Happy debugging.

This question has already been answered. Start a new discussion instead.