/* FileInput.C * * FileInput holds a series of routines to fetch tokens or free text from * the user LaTeX input. * * Copyright 1992 Jonathan Monsarrat. Permission given to freely distribute, * edit and use as long as this copyright statement remains intact. * */ #include #include #include #include #include #include #include "Global.h" #include "Document.h" #include "Font.h" static int comma_delimiter_valid = 0; static int parsing_length = FALSE; static int parsing_command = FALSE; static void usage() { cout << "Usage: lametex [-p psfile] [-d psdir] [ -t ] texfile" << endl; cout << "Use -p psfile to specify the name of the default LameTeX page to use." << endl; cout << "Use -d psdir to specify an additional directory in which" << endl; cout << " to search for LameTeX page definitions." << endl; cout << "Use -t to specify that LameTeX should produce a plain text output" << endl; } FileInput::FileInput(int argc, char *argv[]) { filename[0] = '\0'; pspage[0] = '\0'; current_pspage[0] = '\0'; blankline_area = 1; // Suppress any new paragraphs newline_in_this_blankline_area = 0; vspace_in_this_blankline_area = 0.0; readjust_vspace = 0.0; plain_text_output = 0; // Make an array of directories to look in for LameTeX page descriptions num_pagedirs = 0; add_pagedir("./"); // current directory add_pagedir(PAGEDIR); // compiled-in directory from Makefile char pagedir_names[MAXSTRING]; // Read in environment variable. char *environment_variable; if((environment_variable = getenv("LAMETEX_PS_PATH")) != NULL) strcpy(pagedir_names, environment_variable); // Get all the pagedir paths from the environment variable. char *start = pagedir_names; char *p = strstr(start,":"); while(p) { (*p) = '\0'; add_pagedir(start); start = p+1; p = strstr(start,":"); } add_pagedir(start); for(int arg=1; argisvalid()) { delete file[filenum]; if(filenum == 0) /* Done processing the original file */ return; filenum--; /* Pop up to parent file */ } file[filenum]->get_token(token); } } // Include a file at this point in the flow void FileInput::include_file(char *filename) { /* Has this include file been opened before? */ for(int x=0; x <= filenum; x++) if(file[filenum]->match(filename)) { char message[MAXSTRING]; sprintf(message, "Circular include loop while including file %s", filename); fatal_error(message); } /* Are there too many files currently being processed? */ if(filenum >= MAXFILES-1) { char message[MAXSTRING]; sprintf(message, "Too much include file nesting while including %s", filename); fatal_error(message); } file[++filenum] = new TextFile(filename); } // Prints an error message giving the current file and linenumber, and exits void FileInput::fatal_error(char *errormsg) { file[filenum]->fatal_error(errormsg); } // Prints a warning message giving the current file and linenumber void FileInput::warning(char *errormsg) { file[filenum]->warning(errormsg); } void FileInput::comma_delimiter(int value) { comma_delimiter_valid = value; } void FileInput::set_parsing_length(int value) { parsing_length = value; } void FileInput::add_pagedir(char *dirname) { pagedir[num_pagedirs] = new char [ strlen(dirname) + 1 ]; strcpy(pagedir[num_pagedirs], dirname); // Take off a trailing '/' if needed int length = strlen(pagedir[num_pagedirs]) -1; if(pagedir[num_pagedirs][length] == '/') pagedir[num_pagedirs][length] = '\0'; num_pagedirs++; } void FileInput::use_pspage(char *psname) { strcpy(pspage, psname); } // Force any pending vertical space or newlines to be printed void FileInput::force_space() { float parindent; if(Global::files->newline_in_this_blankline_area > 0) { // If this is a new section, don't indent the first line if(Global::stack->get(Environment::PDocument, Document::JustDidSection,"")) { parindent = Global::stack->get(Environment::PLength, Length::Parameter, "\\parindent"); Global::stack->set(Environment::PLength, Length::Parameter, 0.0, "\\parindent"); } if(Global::files->vspace_in_this_blankline_area > 0.0) Global::files->outfile << endl << "/vspace " << Global::files->vspace_in_this_blankline_area << " def NEWPARA" << endl; else Global::files->outfile << endl << "NEWPARA" << endl; if(Global::stack->get(Environment::PDocument, Document::JustDidSection,"")) Global::stack->set(Environment::PLength, Length::Parameter, parindent, "\\parindent"); Global::files->readjust_vspace = 0.0; } Global::files->blankline_area = 0; Global::files->newline_in_this_blankline_area = 0; Global::files->vspace_in_this_blankline_area = 0.0; } // Check to see if a page has been started, and if not, start one. void FileInput::force_start_page() { // Load new page description (if one has been defined) include_file_ps(pspage, TRUE); // Start new page? if(!Stack::get(Environment::PDocument, Document::StartPage, "")) { outfile << endl; outfile << "STARTPAGE" << endl; Stack::set(Environment::PDocument, Document::StartPage, 1.0, ""); blankline_area = 1; // Suppress any new paragraphs newline_in_this_blankline_area = 0; vspace_in_this_blankline_area = 0.0; Global::files->readjust_vspace = 0.0; } else force_space(); // Force a pending Font command to be executed, if there is one. Stack::set(Environment::PFont, Font::Pending, 0.0, ""); } /* Includes a postscript file in the current output stream. Handles * page definitions properly if it is being asked to load a postscript * file that is a page definition. */ void FileInput::include_file_ps(char *filename, int page_definition) { if(!filename[0] || plain_text_output) return; if(page_definition) if(strcmp(filename, Global::files->current_pspage)==0) { pspage[0] = '\0'; return; } else strcpy(current_pspage, pspage); // First, end the current "formatdict" dictionary on top of the stack outfile << endl << "end" << endl; // Convert the given filename into a full path filename char full_filename[MAXSTRING]; // Get the full pagename path. class stat fileinfo; int x; if(strstr(filename,"/")) { // Does this have any directories specified? if(full_filename[0]=='/') strcpy(full_filename, filename); else sprintf(full_filename, "./%s", filename); } else { // Look for the file in the all given PostScript directories for(x=0; x < num_pagedirs; x++) { sprintf(full_filename, "%s/%s", pagedir[x], filename); if(stat(full_filename,&fileinfo)==0) // Does this file exist? break; } if(x >= num_pagedirs) { // Did not find postscript file char message[MAXSTRING]; sprintf(message, "Cannot find PostScript file %s using path", filename); fatal_error(message); } } // Open the file for reading cerr << "Including PostScript file " << full_filename << endl; ifstream psfile(full_filename); if(!psfile) { // Open file failed? cerr << "Unable to open postscript file " << full_filename << endl; exit (-1); } // Include the PostScript file in the current output char psline[MAXSTRING]; psfile.getline(psline, MAXSTRING, '\n'); while(!psfile.eof() && !psfile.fail()) { outfile << psline << endl; psfile.getline(psline, MAXSTRING, '\n'); } if(page_definition) pspage[0] = '\0'; // Now, start the current "formatdict" dictionary again outfile << "formatdict begin" << endl; } void FileInput::got_whitespace() { file[filenum]->got_whitespace(); } int FileInput::whitespace_next() { return file[filenum]->whitespace_next(); } int FileInput::whitespace_prev() { return file[filenum]->whitespace_prev(); } TextFile::TextFile(char *name) { if(!name) { valid = FALSE; return; } char *p = strstr(name,"."); if(!p) sprintf(filename,"%s.tex",name); else strcpy(filename,name); current_file.open(filename); if(!current_file) { // Open file failed? cerr << "Unable to open LaTeX file " << filename << endl; exit (-1); } cerr << "Processing " << filename << "..." << endl; linenum=1; token_on_this_line = FALSE; just_got_a_newline = FALSE; just_got_whitespace = TRUE; previous_got_whitespace = TRUE; parsing_command = FALSE; valid = TRUE; } TextFile::~TextFile() { current_file.close(); } /* Gets a new token from a file. * If impossible, leaves token marked "invalid". */ void TextFile::get_token(Token& token) { char ch; // We want to set these two flags for every newline, but only // after the token flagged by the newline has been processed! if(just_got_a_newline) { Stack::set(Environment::PDocument, Document::Comment, 0.0, ""); linenum++; just_got_a_newline = FALSE; } if(!isvalid()) return; /* If we're in a postscript environment, dump postscript 'til it closes */ if(Global::stack->get(Environment::PDocument, Document::PostScript, "")) { Global::files->force_start_page(); // Start a new page if not started. char commentline[MAXSTRING]; char *end; int x, stop, comments; comments=0; do { for(x=0, stop=FALSE; x < MAXSTRING && !stop; x++) { current_file.get(ch); switch(ch) { case '\n': comments=0; just_got_a_newline = TRUE; linenum++; commentline[x] = '\0'; stop = TRUE; break; case ' ': just_got_a_newline = FALSE; commentline[x] = '\0'; stop = TRUE; break; default: just_got_a_newline = FALSE; commentline[x] = ch; break; } } end = strstr(commentline,"\\end{postscript}"); if(end) { (*end) = '\0'; } if(commentline[0] == '%' && !comments) { Global::files->outfile << &commentline[1]; // Skip initial '%' comments++; } else Global::files->outfile << commentline; Global::files->outfile << (char) ch; } while(!end && !current_file.eof() && !current_file.fail()); Stack::pop(0, Document::End, 0.0, "\\postscript"); } /* If we're in an ignore environment, do nothing 'til it closes */ if(Global::stack->get(Environment::PDocument, Document::Ignore, "")) { char commentline[MAXSTRING]; char *end; int x, stop, comments; comments=0; do { for(x=0, stop=FALSE; x < MAXSTRING && !stop; x++) { current_file.get(ch); switch(ch) { case '\n': comments=0; just_got_a_newline = TRUE; linenum++; commentline[x] = '\0'; stop = TRUE; break; case ' ': just_got_a_newline = FALSE; commentline[x] = '\0'; stop = TRUE; break; default: just_got_a_newline = FALSE; commentline[x] = ch; break; } } end = strstr(commentline,"\\end{ignore}"); } while(!end && !current_file.eof() && !current_file.fail()); Stack::pop(0, Document::End, 0.0, "\\ignore"); } int pos = 0; int token_started = FALSE; int in_a_number = FALSE; previous_got_whitespace = just_got_whitespace; // Loop through characters in the file unless some file error occurs. for(current_file.get(ch); ch && !current_file.eof() && !current_file.fail(); current_file.get(ch)){ if(just_got_a_newline) { Stack::set(Environment::PDocument, Document::Comment, 0.0, ""); linenum++; just_got_a_newline = FALSE; } switch(ch) { case '\\': case '{': case '}': case '[': case ']': break; default: if(!parsing_command && ch != '\\') just_got_whitespace = isspace(ch); break; } switch(ch) { // What is the character? case '%': // The special comment character if(token_started) { // If currently inside a token, it's current_file.putback(ch); token_text[pos++] = '\0'; // interpreted as an end-token token.make_text(token_text); // Successfully got a token. return; } else Stack::set(Environment::PDocument, Document::Comment, 1.0, ""); break; case '\n': just_got_a_newline = TRUE; // End a \STEALTH with a newline if(Stack::get(Environment::PDocument, Document::Stealth, "")==2.0) Stack::set(Environment::PDocument, Document::Stealth, 0.0, ""); if(!token_on_this_line) { // Is this text line entirely whitespace token_text[0] = '\0'; // If so, return the blank line token "". token.make_text(token_text); return; } token_on_this_line = FALSE; case ' ': case '\t': if(token_started) { // Marks back or front of token? token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. return; } parsing_command = FALSE; break; case '{': case '[': case '}': case ']': token_on_this_line = TRUE; if(token_started) // Marks back or front of token? if(pos==1 && token_text[0] == '\\') // Is the token "\{" or "\{" ? token_text[pos++] = ch; else { // We must do look-ahead to set just_got_whitespace. // What is the first character after the '}' character(s)? current_file.putback(ch); } else { token_text[pos++] = ch; if(ch == '}' && !parsing_command) { for(int x=0; ch == '}'; x++) current_file.get(ch); just_got_whitespace = isspace(ch); current_file.putback(ch); for(int y=0; y < x-1; y++) current_file.putback('}'); } } parsing_command = FALSE; token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. return; case '\\': parsing_command = TRUE; if(token_started) { current_file.putback(ch); token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. return; } token_started = TRUE; token_text[pos++] = ch; current_file.get(ch); // Look for special 2 character commands. switch(ch) { case '\\': case '&': case '%': case '#': case '{': case '}': case '_': token_text[pos++] = ch; token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. current_file.get(ch); just_got_whitespace = isspace(ch); current_file.putback(ch); return; default: current_file.putback(ch); break; } break; case ',': if(comma_delimiter_valid && token_started) { token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. return; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': case '-': if(parsing_length && token_started && !in_a_number) { token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. current_file.putback(ch); return; } in_a_number = TRUE; token_on_this_line = TRUE; if(!token_started) token_started = TRUE; if(pos < MAXSTRING - 1) token_text[pos++] = ch; else { cerr << "File contains words that are longer than " << MAXSTRING << " characters!" << endl; exit(-1); } break; // These are characters we want to skip, but they can define // the ends of tokens. case '$': case '#': case '~': case '&': case '^': if(token_started) { token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. parsing_command = FALSE; return; } break; case ':': case ';': case '?': case '!': case '`': case '_': case '\'': case '(': case ')': case '/': case '*': case '@': case '+': case '=': case '|': case '<': case '>': case '"': parsing_command = FALSE; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': if(parsing_length && token_started && in_a_number) { token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. current_file.putback(ch); return; } token_on_this_line = TRUE; if(!token_started) token_started = TRUE; if(pos < MAXSTRING - 1) token_text[pos++] = ch; else { cerr << "File contains words that are longer than " << MAXSTRING << " characters!" << endl; exit(-1); } break; default: cerr << "File contains illegal character " << (int) ch << endl; exit(-1); break; } } valid = FALSE; // Reached END OF FILE if(token_started) { token_text[pos++] = '\0'; token.make_text(token_text); // Successfully got a token. } } int TextFile::isvalid() { return(valid); } int TextFile::match(char *name) { return(!strcmp(name, filename)); } void TextFile::fatal_error(char *errormsg) { cerr << "\"" << filename << "\", line " << linenum << ": error at \"" << token_text << "\": " << errormsg << endl; exit(-1); } void TextFile::warning(char *errormsg) { cerr << "\"" << filename << "\", line " << linenum << ": warning at \"" << token_text << "\": " << errormsg << endl; } void TextFile::got_whitespace() { just_got_whitespace = 1; } int TextFile::whitespace_next() { return just_got_whitespace; } int TextFile::whitespace_prev() { return previous_got_whitespace; }