home *** CD-ROM | disk | FTP | other *** search
- /* 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 <ctype.h>
- #include <string.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include "Global.h"
- #include "Document.h"
- #include "Font.h"
- #include "Operator.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; arg<argc; arg++) {
- if(argv[arg][0] == '-') {
- switch (argv[arg][1]) {
- case 'd':
- case 'D':
- add_pagedir(argv[++arg]);
- case 'p':
- case 'P':
- strcpy(pspage,argv[++arg]); // -p gives default pspage
- break;
- case 't':
- case 'T':
- plain_text_output = 1;
- break;
- default:
- usage ();
- }
- }
- else
- strcpy(filename,argv[arg]);
- }
-
- if(!filename[0]) {
- cerr << "No files given to process." << endl;
- exit(0);
- }
-
- filenum = 0;
-
- if(!pspage[0]) // The default page template
- strcpy(pspage,"page_latex.ps");
-
- /* Parse input file name to get output file name */
- strcpy(outfileroot,filename);
- p = strstr(outfileroot,".");
- if(p)
- (*p)='\0';
- p = strrchr(outfileroot,'/');
- if(p)
- sprintf(outfilename,"%s.PS",p+1);
- else
- sprintf(outfilename,"%s.PS",outfileroot);
-
- /* Open files for reading and writing */
- file[0] = new TextFile(filename);
- cerr << "Opening " << outfilename << " for temporary output..." << endl;
- outfile.open(outfilename);
- }
-
- // Gets a new token from a list of files. If impossible, marks token invalid.
- void FileInput::get_token(Token &token)
- {
- while(!token.isvalid()) {
- if(!file[filenum]->isvalid()) {
- 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 a verbatim environment, dump text 'til it closes */
- if(Global::stack->get(Environment::PDocument, Document::Verbatim, "")) {
- Global::files->force_start_page(); // Start a new page if not started.
- Global::files->outfile << " NEWPARA" << endl;
- char commentline[MAXSTRING];
- char *end;
- do {
- current_file.getline(commentline, MAXSTRING, '\n');
- just_got_a_newline = TRUE;
- linenum++;
-
- end = strstr(commentline,"\\end{verbatim}");
-
- if(end) {
- (*end) = '\0';
- }
- char output[MAXSTRING];
- Operator::registrar(commentline, output);
- Global::files->outfile << output << " VERBATIM" << endl;
- }
- while(!end && !current_file.eof() && !current_file.fail());
- Stack::pop(0, Document::End, 0.0, "\\verbatim");
- }
-
- /* 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;
- }
-