//*****************************************************************************
//****************** 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);
}