//*****************************************************************************
//****************** TFC.CC: Text Format Conversion Utility *******************
//*****************************************************************************
//************************** <C> 1991 by M.Hofmann ****************************
//*****************************************************************************

//*** 18.01.1994  21:34:18

//*** How To Compile:
//***    Use C++ Compiler!
//***    IBM-DOS: Compile with TINY-model
//***             Borland-C:  bcc -mt -O -G -lt -Z -d -w tfc.cc
//***    UNIX:    GNU-C++:    g++ -O2 -m486 -o /usr/bin/tfc tfc.cc      
//***    VMS:     DEC-CXX:    cxx /DEFINE=VMS /OPTIMIZE tfc.cc 

#define  TFC

#ifdef __TURBOC__
        #ifndef __TINY__
                #error compile only with tiny model !!
        #endif
#else
        #ifdef VMS
                #define unlink(a) remove(a)
        #else   
                #include <unistd.h>
        #endif
#endif

// Include standard header files

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

//*****************************************************************************
//******** Messages and error messages ****************************************
//*****************************************************************************

char license_msg[] = 
        { "This program is free software; you can redistribute it and/or modify\n"
          "it under the terms of the GNU General Public License as published by\n"
          "the Free Software Foundation; either version 2 of the License, or\n"
          "(at your option) any later version.\n\n"

          "This program is distributed in the hope that it will be useful,\n"
          "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
          "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
          "GNU General Public License for more details.\n\n"

          "You should have received a copy of the GNU General Public License\n"
          "along with this program; if not, write to the Free Software\n"
          "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n"};

char help_msg[] =
        { "\n  TFC converts text files between various file formats \n\n"
          "  Syntax: TFC [-ADELPUVX?] [-T [n]] [-N [n]] input_file [output_file]\n\n"
          "     -u    : DOS -> Unix:   Replace all CR/LF with LF, delete Ctrl-Z\n"
          "     -d    : Unix -> Dos:   Replace all LF with CR/LF\n"
          "     -a    : DOS -> ASCII:  Change DOS-Format to 7 Bit-ASCII-Format\n"
          "     -n [n]: Number lines:  Number output lines. n: start line number\n"
          "     -t [n]: Expand tabs:   Convert tabs to spaces. n: tab spacing (default 8)\n"
          "     -v    : Text -> LaTeX: Generate a LaTeX verbatim list\n"
          "     -x    : DOS -> LaTeX:  Convert DOS special characters to LaTeX format\n"
          "     -p    : par mode       Check parentheses\n\n"
          "     -l    : license        Show software license\n"
          "     -q    : quiet:         No output on screen during operation\n\n"
          "  This is free software, and you are welcome to redistribute it\n"
          "  under certain conditions; type `tfc -l' for details.\n"
          "  If ouput_file is omitted, output is sent to input_file. This overwrites\n" 
          "  input_file, so be careful!  Return Code: 0 on success\n"};

#define  MsgTitle1    "\nTFC: Text Format Conversion Utility\n"
#define  MsgTitle2    "Version 1.3 <C> Copyright " __DATE__ " by M.Hofmann\n"

#define  MsgOk        "\nProgram completed with no errors\n\n"
#define  MsgOpenF     "\n\nOpening files:\n"
#define  MsgIFile     "\n    Input file ======> (buffer length: %4XH) %s"
#define  MsgOFile     "\n    Output file  ====> (buffer length: %4XH) %s"
#define  MsgRen       "\n    Output file will be renamed to %s after completion"
#define  MsgConv      "\n\n\nConverting data:\n\n"
#define  MsgLComp     "\r    %i lines completed"

#define  MsgOpenPar   "WARNING: Unequal number of parentheses found (%i more '%c' than '%c')...\x07\n"
#define  MsgOddQuot   "WARNING: Odd number of \x22 detected...\x07\n"

#define  ErrNoArg     "Wrong number of arguments"
#define  ErrNoOpt     "Unknown Option specified"
#define  ErrNoInFile  "Can't open input file"
#define  ErrNoInName  "No input file name specified"
#define  ErrNoOutFile "Can't open output file"
#define  ErrClFile    "Can't close files"
#define  ErrReadF     "Error reading file"
#define  ErrWrtF      "Error writing file"
#define  ErrNoRename  "Can't rename file"

//*****************************************************************************
//******** Some useful definitions ********************************************
//*****************************************************************************

#define true  (-1)
#define false (0)

typedef unsigned char byte;                             //  8 bits
typedef unsigned int  word;                             // 16 bits

//*****************************************************************************
//******* Variables ***********************************************************
//*****************************************************************************

// Input / Output files

char    iname[100],oname[100];
FILE    *ifile, *ofile;
int     renflag = false;

// Line / column counting

int     lstart = 1;
int     lcount = 0;                                     // actual line of output text
int     pos = 0;                                        // actual column of output text

// Flags for operation modes

int     unixmode    = false;                            // Flags for conversion modes
int     dosmode     = false;
int     texlistmode = false;
int     nummode     = false;
int     ascmode     = false;
int     texmode     = false;
int     qmode       = false;
int     parmode     = false;
int     tabmode     = false;
int     tabdist     = 8;

// Conversion tables

char    dos2unix[256][10];
char    dos2tex[256][10];

char    cr_str[10];                                     // String with proper CR/ CR/LF sequence

// Other global variables

int     parenum = 0, pargnum = 0, parrnum = 0;          // number of open braces
int     quotenum = 0;                                   // number of double quotes (")

//*****************************************************************************
//******* Input / Output functions ********************************************
//*****************************************************************************

void    ExitErr(char *errmsg,int ErrNum)

/*** Show an error message and quit program ***/

{
        fflush (stdout);
        fprintf (stderr, "\n\nError:  %s !!!\n\n", errmsg);
        fprintf (stderr, "Aborting program (ERC>0)....\n\n");
        fprintf (stderr, "Syntax: TFC [-ADELPUVX?] [-T [n]] [-N [n]] input_file [output_file]\n");
        fprintf (stderr, "        TFC -? for help\n\n");
        exit(ErrNum);
}

void    title()

/*** Print title ***/

{
        if (!qmode) {
                printf (MsgTitle1);                     // Print title
                printf (MsgTitle2);
        }
}

void    help()

/*** Print Help page and exit ***/

{
        printf(help_msg);
        exit(0);
}

void    license()

/*** Print license and exit ***/

{
        printf("\n\nLICENSE:\n\n%s", license_msg);
        exit(0);
}

//*****************************************************************************
//****** File I/O *************************************************************
//*****************************************************************************

void    fileo(char *iname, char *oname)

/*** Open files iname and oname for reading and writing. If oname=="", ***/
/*** write to file temp and rename temp to iname when closing files ***/

{
        renflag = (oname[0]==0);
        if (renflag) strcpy(oname, "temp");

        if (!qmode) {
                printf (MsgOpenF);
                printf (MsgIFile MsgOFile,20000,iname,20000,oname);
                if (renflag) printf (MsgRen, iname);
        } else {
                printf ("TFC:  %s \t %s", iname, oname);
                if (renflag) printf (" --> %s", iname);
                printf ("\n");
        }
        if ((ifile=fopen(iname,"rb"))==NULL) ExitErr(ErrReadF,1);
        if ((ofile=fopen(oname,"wb"))==NULL) ExitErr(ErrWrtF,1);
        setvbuf(ifile,NULL,_IOFBF,20000);               // Faster with buffers
        setvbuf(ofile,NULL,_IOFBF,20000);
}

void    filec()

/*** Close files and replace input file if inputfile=outputfile ***/

{
        if (fclose(ofile)) ExitErr(ErrClFile,1);
        if (fclose(ifile)) ExitErr(ErrClFile,1);

        if (renflag) {
                if (0!=unlink(iname)) ExitErr(ErrNoRename,1);
                if (0!=rename(oname,iname)) ExitErr(ErrNoRename,1);
        }
}

void    fp(byte c)

/*** Write byte c to outputfile. Handle CR / LF ***/

{
        switch (c) {
                case '\n' : if (EOF==fputs(cr_str,ofile)) ExitErr(ErrWrtF,1);
                            pos = 0;
                            lcount++;
                            break;
                case 13   : break;
                default   : if (EOF==fputc(c,ofile)) ExitErr(ErrWrtF,1);
                            pos++;
                            break;
        }
}

void    fps(char *s)

/*** Write string s to outputfile ***/

{
        while(*s) fp(*s++);
}

//*****************************************************************************
//****** System Initialization ************************************************
//*****************************************************************************

void    setuptables()

/*** Setup translation tables ***/

{
         int i;

         for (i=0; i<127; i++) {
                 sprintf (dos2unix[i],    "%c",i);
                 sprintf (dos2unix[i+128],"_");
                 sprintf (dos2tex[i],     "%c",i);
                 sprintf (dos2tex[i+128], "_");
         }

         strcpy (dos2unix[148], "oe");          strcpy (dos2unix[132], "ae");
         strcpy (dos2unix[129], "ue");          strcpy (dos2unix[153], "Oe");
         strcpy (dos2unix[142], "Ae");          strcpy (dos2unix[154], "Ue");
         strcpy (dos2unix[225], "ss");

         strcpy (dos2tex[ 21], "\\s{}");        strcpy (dos2tex[135], "\\c{c}");
         strcpy (dos2tex[160], "\\'a");         strcpy (dos2tex[133], "\\`a");
         strcpy (dos2tex[131], "\\^a");         strcpy (dos2tex[130], "\\'e");
         strcpy (dos2tex[136], "\\^e");         strcpy (dos2tex[137], "\x22""e");
         strcpy (dos2tex[138], "\\`e");         strcpy (dos2tex[147], "\\^o");
         strcpy (dos2tex[163], "\\'u");         strcpy (dos2tex[151], "\\`u");
         strcpy (dos2tex[150], "\\^u");         strcpy (dos2tex[139], "\x22""i");
         strcpy (dos2tex[140], "\\^i");         strcpy (dos2tex[148], "\x22""o");
         strcpy (dos2tex[132], "\x22""a");      strcpy (dos2tex[129], "\x22""u");
         strcpy (dos2tex[153], "\x22""O");      strcpy (dos2tex[142], "\x22""A");
         strcpy (dos2tex[154], "\x22""U");      strcpy (dos2tex[164], "\\~n");
         strcpy (dos2tex[165], "\\~N");         strcpy (dos2tex[168], "?`");
         strcpy (dos2tex[173], "!`");           strcpy (dos2tex[225], "\x22""s");
         strcpy (dos2tex[224], "$\\alpha$");    strcpy (dos2tex[248], "$^0$");

// Determine proper LF sequence (DOS needs CR/LF)

         #ifdef __MSDOS__                       //
                strcpy (cr_str,"\x0D\x0A");
         #else
                strcpy (cr_str,"\x0A");
         #endif
         if (dosmode)
                strcpy (cr_str,"\x0D\x0A");
         if (unixmode)
                strcpy (cr_str,"\x0A");
}

//*****************************************************************************
//******* Translate Input File ************************************************
//*****************************************************************************

void    convchar(int c)

/*** Translates the character c and writes it to the output file ***/

{
// Check for ASCII Control codes (c<20h)

        if (unixmode && c==26) return;

        if (tabmode && c==9) {
                do { fp (' '); } while (pos % tabdist);
                return;
        }

// Check brace level

        if (parmode) {
                switch(c) {
                        case  34 : quotenum++;break;  /* " */
                        case  40 : parrnum++;break;   /* ( */
                        case  41 : parrnum--;break;   /* ) */
                        case  91 : parenum++;break;   /* [ */
                        case  93 : parenum--;break;   /* ] */
                        case 123 : pargnum++;break;   /* { */
                        case 125 : pargnum--;break;   /* } */
                } /* while */
        }

// ASCII-Translation ?

        if (ascmode) {
                fps (dos2unix[c]);
                return;
        }

// TeX-Translation ?

        if (texmode) {
                fps (dos2tex[c]);
                return;
        }

// No Translation !

        fp(c);
}

void    convtext()

{
        int  register c = 0;
        char s[10];

        if (!qmode)
                printf (MsgConv);                       // print message

        if (texlistmode) {                              // if Latex List mode: print start commands
                fps ("%***\n");
                fps ("%*** File:  "); fps (iname);
                fps ("\n%*** Formatted for:  LaTeX  by TFC V1.3 ("__DATE__")\n%***\n");
                fps ("{\\scriptsize\n\\begin{verbatim}\n");
                lstart-=lcount;
        }

        while (EOF!=(c=fgetc(ifile))) {
                if (c==26) break;

// Start of a new line ?

                if (pos==0) {
                        if (!((lcount-1) % 64) && !qmode) // Print line number
                                printf(MsgLComp,lcount);

                        if (nummode) {                  // Number lines in output file
                                sprintf(s,"%5i: ",lcount+lstart);
                                fps(s);
                        }
                }

// Translate and output character

                convchar (c);
        } /* while */

// Ok, the whole file was translated. Print messages to user

        if (!qmode) {
                printf(MsgLComp,lcount);
                printf ("\n\n");
        }
        if (ferror(ifile)) ExitErr(ErrReadF,1);

        if (texlistmode)                                 // End of LaTeX environment
               fps ("\\end{verbatim}\n}");

        if (parmode) {
               if (quotenum % 2) printf (MsgOddQuot);                 // Even Number of quotes?
               if (pargnum>0) printf (MsgOpenPar, pargnum,'{','}');   // All Braces closed?
               if (pargnum<0) printf (MsgOpenPar,-pargnum,'}','{');
               if (parenum>0) printf (MsgOpenPar, parenum,'[',']');
               if (parenum<0) printf (MsgOpenPar,-parenum,']','[');
               if (parrnum>0) printf (MsgOpenPar, parrnum,'(',')');
               if (parrnum<0) printf (MsgOpenPar,-parrnum,')','(');
        }
}

//*****************************************************************************
//****** Main program *********************************************************
//*****************************************************************************

int     main (int argc, char *argv[])

{
        int   i, j;
        char  c;

        iname[0] = oname[0] = '\0';                     // No filenames so far

// Scan input line

        for (i=1; i<argc; i++) {                        // Scan input line for arguments
                if (argv[i][0] == '-') {                // is it an option ?
                        for (j = 1; j > 0 && 0 != (c=toupper(argv[i][j])); j++) {
                                switch(c) {
                                        case ('U') : unixmode = true; break;
                                        case ('D') : dosmode = true; break;
                                        case ('A') : ascmode = true; break;
                                        case ('P') : parmode = true; break;
                                        case ('Q') : qmode = true; break;
                                        case ('V') : texlistmode = true; break;
                                        case ('X') : texmode = true; break;
                                        case ('H') :
                                        case ('?') : title();help(); break;
                                        case ('L') : title();license(); break;
                                        case ('N') : nummode = true;
                                                     if (1 == sscanf (argv[i+1], "%d", &lstart)) {
                                                        i++; j = -1;
                                                     }
                                                     break;
                                        case ('T') : tabmode = true;
                                                     if (1 == sscanf (argv[i+1], "%d", &tabdist)) {
                                                        i++; j = -1;
                                                     }
                                                     break;
                                        default    : title();
                                                     ExitErr(ErrNoOpt,1);
                                } /* switch */
                        }
                } else {                                // Seems to be a file name
                        if (iname[0]==0) {
                                strcpy(iname,argv[i]);
                        } else {
                                if (oname[0]!=0)
                                        ExitErr(ErrNoArg,1);
                                strcpy(oname,argv[i]);
                        }
                } /* if */
        } /* for */

// Print Title

        title();                                        // Print title message

// Check parameters and options

        if (!iname[0]) ExitErr(ErrNoInName,1);

// Initialize system

        setuptables();

// Convert text file

        fileo(iname,oname);                             // Open files
        convtext();                                     // Convert input file
        filec();                                        // Close file

// No error occurred: Prepare for exit

        if (!qmode)
                printf (MsgOk);
        exit(0);
}