/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
 * flatex.c -
 *    Flatten a latex file into a single file, by explicitly including 
 *  the files inclued by \include and \input commands. Also, if bibtex is
 *  beeing used, then includes the .bbl file into the resulting file. Thus,
 *  creating a stand alone latex file that can be emailed to someone else. 
 *
 * Compile    : gcc -o flatex flatex.c
 * Tested on  : Linux + gcc   
 * By         : Sariel Har-Peled 
 * Email      : sariel@math.tau.ac.il
 * WEB Page   : http://www.math.tau.ac.il/~sariel/flatex.html
 * Status     : You can do whatever you like with this program. please 
 *              email me bugs & suggestions. 
 *
 * To do      : Add support to the includeonly command.
 *-----------------------------------------------------------------------
 * FLATEX  1.21, 1994, 1996, by Sariel Har-Peled.
 *
 * flatex - create a single latex file with no include/inputs
 *
 *       flatex [-v] [-x FileName] [files]
 *               -v      Verbose, display file structure.
 *               -x      Unflatex: extract files from archive
 *               -q      Quiet mode. Cleaner output but -x can not be used.
 *               -b      Do not insert bibiliography file(.bbl)
 * 
 * Flatex page: http://www.math.tau.ac.il/~sariel/flatex.html
 *-----------------------------------------------------------------------
 * History:
 *    26/8/96, 1.21
 *         Fixed bug with includegraphics command.
\*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/

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


/*======================================================================
 * Static constants.
\*======================================================================*/
#define  LINE_SIZE 1000
#define  FALSE     0
#define  TRUE      1
#define  USE_ARGUMENT( X )   ((void)X)


/*======================================================================
 * Types
\*======================================================================*/
typedef struct {
  char    verbose;
  char    fBibInsert, fQuiet;
  int     cSpecialInputLevel;
  char    szFullName[ LINE_SIZE ];
}  structFlags;


/*======================================================================
 * Static prototypes.
\*======================================================================*/
static void        flatIt( FILE         * flOut,
                           char         * szInName,
		           int            level,
	                   structFlags  * pFlags );
static void        replaceExt( char     * str, char    * ext );


/*======================================================================
 * Start of Code
\*======================================================================*/


static void        spacesByLevel( int     level )
{
  while  ( level > 0 ) {
    printf( "    " );
    level--;
  }
}


static void          printHelp( void )
{
    printf( "flatex - create a single latex file with no include/inputs\n" );
    printf( "\n\tflatex [-v] [-x FileName] [files]\n" );
    printf( "\t\t-v\tVerbose, display file structure.\n" );
    printf( "\t\t-x\tUnflatex: extract files from archive\n" );
    printf( "\t\t-q\tQuiet mode. Cleaner output but -x can not be used.\n" );
    printf( "\t\t-b\tDo not insert bibiliography file(.bbl)\n" );
    printf( "\nFlatex page: http://www.math.tau.ac.il/~sariel/flatex.html\n" );
    printf( "\n" );
}


static void      * myMalloc( unsigned int      size )
{
    void         * ptr;

    ptr = malloc( size );
    if  ( ptr == NULL ) {
        fprintf( stderr, "Not enough memory" );
        exit( -1 );
    }

    return    ptr;
}


static void        handleIncludeCommand( char         * line,
                                         char         * lpszInclude,
                                         FILE         * flOut,
                                         int            level,
                                         structFlags  * pFlags )
{
    char       * lpszBrace, * lpszName, * lpszEndBrace;
    char         ch, fInput = 0;

    lpszBrace = NULL;

    if  ( strncmp( lpszInclude, "\\input", 6 ) == 0 ) {
        lpszBrace = lpszInclude + 6;
	fInput = 1;
    } else
        if  ( strncmp( lpszInclude, "\\include", 8 ) == 0 ) {
            lpszBrace = lpszInclude + 8;
        }

    ch = *lpszInclude;
    *lpszInclude = 0;
    fputs( line, flOut );
    *lpszInclude = ch;

    lpszEndBrace = strchr( lpszBrace, '}' );
    if  ( *lpszBrace != '{'  ||  lpszEndBrace == NULL ) {
        fprintf( stderr, "ERROR: Expected brace not found.\n\n\tline:%s\n",
                 line );
        exit( -1 );
    }

    *lpszEndBrace = 0;
    lpszName = (char *)myMalloc( LINE_SIZE );
    strcpy( lpszName, lpszBrace + 1 );
    if  ( ! fInput )  
        replaceExt( lpszName, ".tex" );

    flatIt( flOut, lpszName, level + 1, pFlags );

    lpszEndBrace++;
    while  ( *lpszEndBrace ) {
        *line++ = *lpszEndBrace++;
    }
    *line = 0;

    free( lpszName );
}


static char        isBefore( char    * lpszA, char    * lpszB )
{
    if  ( lpszB == NULL )
        return  TRUE;
    if  ( lpszA == NULL )
        return  FALSE;

    if  ( (int)( lpszA -lpszB ) < 0 ) {
        return   TRUE;
     }
    return   FALSE;
}


static FILE      * fopenTex( char         * file, 
                             char         * mode )
{
    FILE     * fl;

    fl = fopen( file, mode );
    if  ( fl != NULL ) 
      return fl;
    
    replaceExt( file, ".tex" );
    fl = fopen( file, mode );

    return  fl;
}


static char      isTexFileExists( char        * file )
{
    FILE     * fl;

    fl = fopenTex( file, "rt" );
    if  ( fl != NULL ) {
      fclose( fl );
      return  1;
    }

    return  0;
}


static void        addTexExt( char    * file )
{
    FILE      * fl;

    fl = fopenTex( file, "rt");
    if  ( fl != NULL )
      fclose( fl );
}


static char     is_str_prefix( char    * str, char * prefix )
{
    int len;

    if  ( str == NULL  ||  prefix == NULL )
        return  0;

    len = strlen( prefix );
    
    return  (strncmp( str, prefix, len ) == 0);
}


static void        flatIt( FILE         * flOut,
                           char         * pSzInName,
		           int            level,
                           structFlags  * pFlags )
{
    FILE      * flIn;
    char      * str, * lpszInput, * lpszInclude, * line, * lpszRem, *inc;
    char      * lpszLine, * lpszRemark, * lpszBib, * lpszBibStyle;
    char      * lpszNewCommand, * lpszName;
    char        cont;
    char        repFlag;
    char        szInName[ 100 ];
    char        fInclude;

    strcpy( szInName, pSzInName );

    addTexExt( szInName );
    if  ( ! pFlags->fQuiet ) 
      fprintf( flOut, "%%%cflatex input: [%s]\n", 
               pFlags->cSpecialInputLevel > 0? '*' : ' ', 
               szInName ); 
    if  ( pFlags->verbose ) {
        printf( "\t" );
        spacesByLevel( level );
        printf( "%s\n", szInName );
    }

    line = (char *)myMalloc( LINE_SIZE );
    lpszLine = (char *)myMalloc( LINE_SIZE );
    lpszRemark = (char *)myMalloc( LINE_SIZE );

    flIn = fopenTex( szInName, "rt" );
    if   ( flIn == NULL ) {
        fprintf( stderr, "Unable to open file: %s\n", szInName );
        exit( -1 );
    }

    *lpszRemark = 0;
    while  ( ! feof( flIn ) ) {
        str = fgets( line, LINE_SIZE, flIn );
        if  ( str == NULL )
            break;

        fInclude = FALSE;

        strcpy( lpszLine, line );

        lpszRem = strchr( line, '%' );
        if  ( lpszRem != NULL ) {
            strcpy( lpszRemark, lpszRem );
            *lpszRem = 0;
        }

        do  {
            cont = 0;
            lpszInput = strstr( line,   "\\input" );

            lpszBib = strstr( line, "\\bibliography" );
            lpszBibStyle = strstr( line, "\\bibliographystyle" );

            if  ( pFlags->fBibInsert  &&
                  ( lpszBib != NULL  ||  lpszBibStyle != NULL ) ) {
                lpszName = (char *)myMalloc( LINE_SIZE );
                
                strcpy( lpszName, lpszLine );
                strcpy( lpszLine, pFlags->fQuiet? "%" : "%FLATEX-REM:" );
                strcat( lpszLine, lpszName );

                if  ( lpszBibStyle != NULL ) {
                    strcpy( lpszName, pFlags->szFullName );
                    replaceExt( lpszName, ".bbl" );
 
                    pFlags->cSpecialInputLevel++;
                    flatIt( flOut, lpszName, level + 1, pFlags );
                    pFlags->cSpecialInputLevel--;
                    
                    if  ( pFlags->verbose ) {
                        printf( "\t" );
                        spacesByLevel( level + 1 );
                        printf( "(Bibiliography)\n" );
                    }
                }
                break;
            }
 
            inc = line;
            do { 
                repFlag = 0;
                lpszInclude = strstr( inc, "\\include" );

                if  ( is_str_prefix( lpszInclude, "\\includeversion" )  
                      ||  is_str_prefix( lpszInclude, 
                                         "\\includegraphics"  ) ) {
                    repFlag = 1;
                    inc = lpszInclude + 1;
                    continue;
                }
                
                if  ( is_str_prefix( lpszInclude, "\\includeonly" ) ) {
                    fprintf( stderr, "WARNING: \"\\includeonly\" command "
                             "ignored\n" );
                    inc = lpszInclude + 1;
                    repFlag = 1;
                    continue;
                }
                if  ( lpszInclude != NULL  &&  isalpha( lpszInclude[ 8 ] ) ) {
                    fprintf( stderr,
                      "\nWarning: include-like(?) command ignored"
                             " at line:\n\t%s", lpszLine );
                    inc = lpszInclude + 1;
                    repFlag = 1;
                    continue;
                }
            }  while  ( repFlag );
 
            if  ( isBefore( lpszInput, lpszInclude ) )
                lpszInclude = lpszInput;

            if  ( lpszInclude != NULL ) {
                lpszNewCommand = strstr( line, "\\newcommand" );
                if (  lpszNewCommand == NULL )  {
                    handleIncludeCommand( line, lpszInclude, flOut, level,
                                          pFlags );
                    cont = 1;
                    fInclude = TRUE;
                }
            }
        } while  ( cont );
        if  ( fInclude ) {
            strcat( line, lpszRemark );            
            fputs( line, flOut );
        } else
            fputs( lpszLine, flOut );
    }

    fclose( flIn );
    fputs( "\n", flOut );

    if  ( ! pFlags->fQuiet ) 
      fprintf( flOut, "%% flatex input end: [%s]\n", szInName );

    free( line );
    free( lpszLine );
    free( lpszRemark );
}


static void        replaceExt( char     * str, char    * ext )
{
    int        len, ind;

    len = strlen( str );
    ind = len - 1;
    while  ( ind >= 0  &&  str[ ind ] != '.'  &&  str[ ind ] != '\\' &&
             str[ ind ] != '/' )
        ind--;

    if  ( ind >= 0  &&  str[ ind ] == '.' ) {
        str[ ind ] = 0;
    }

    strcat( str, ext );
}

static char         strCmpPrefixAndCopy( char      * line, 
                                         char      * str,
                                         char      * outName )
{
  char       * pos, * pPreLine;

  pPreLine = line;

  pos = strstr( line, str );
  if  ( pos == NULL )
    return  0;

  line = pos + strlen( str );
  strcpy( outName, line );
  pos = strchr( outName, ']' );

  if  ( pos == NULL ) {
    fprintf( stderr, "Error encountered in line: [%s]", pPreLine );
    exit( -1 );
  }
  *pos = 0;

  return  1;
}


static void        writeFile( FILE     * flIn, 
                              char     * pOutName, 
                              int        level )
{
  FILE       * flOut;
  char       * lpszLine;
  char         line[ LINE_SIZE ], outName[ LINE_SIZE ];
  char         flag;

  outName[ 0 ] = 0;

  if  ( pOutName == NULL ) {
    flOut = NULL;
    printf( "Scanning for flatex archive start...\n" );
  } else {
    flOut = fopen( pOutName, "wt" );
    if  ( flOut == NULL ) {
      fprintf( stderr, "Unable to open file: %s", pOutName );
      exit( -1 );
    }
    spacesByLevel( level );
    printf( "[%s]\n", pOutName );
  }

  do {
    lpszLine = fgets( line, LINE_SIZE, flIn );
    if  ( lpszLine == NULL ) 
      break;

    flag = strCmpPrefixAndCopy( line, "% flatex input end: [", outName ); 
    if  ( flag ) {
      if  ( flOut == NULL ) {
        fprintf( stderr, "Something is wrong!!!!\n" );
        exit( -1 );
      }
      //spacesByLevel( level );
      //     printf( "/\n" );
      //printf( "Writing [%s] done\n", outName );
      break;
    }

    flag = strCmpPrefixAndCopy( line, "% flatex input: [", outName ); 
    if  ( flag ) {
        writeFile( flIn, outName, level + 1 );
        if  ( flOut != NULL ) 
          fprintf( flOut, "\\input{%s}\n", outName );
    } else {
        flag = strCmpPrefixAndCopy( line, "%*flatex input: [", outName ); 
        if  ( flag ) {
            writeFile( flIn, outName, level + 1 );
        } else {
            if  ( flOut != NULL ) {
                if  ( strncmp( line, "%FLATEX-REM:", 12 ) == 0 ) 
                    fputs( line + 12, flOut );
                else
                    fputs( line, flOut );
            }
        }
    }
  }  while  ( ! feof( flIn ) );
  
  if  ( flOut != NULL ) 
    fclose( flOut );  
}


static void        flatOutFile( char         * fileName, 
                                structFlags  * pFlags )
{
  FILE       * flIn;

  USE_ARGUMENT( pFlags );

  flIn = fopen( fileName, "rt" );
  if  ( flIn == NULL ) {
    fprintf( stderr, "Unable to open file: %s", fileName );
    exit( -1 );
  }
  
  writeFile( flIn, NULL, 0 );

  fclose( flIn );
}


static void        flatFile( char         * fileName, 
                             structFlags  * pFlags )
{
    char             * szInName, * szOutName;
    int                inLen;
    FILE             * flOut;

    szInName = (char *)myMalloc( LINE_SIZE );
    szOutName = (char *)myMalloc( LINE_SIZE );

    strcpy( szInName, fileName );
    if  ( ! isTexFileExists( szInName ) ) {
      fprintf( stderr, "--Unable to open file: [%s]\n", fileName );
      exit( -1 );
    }

    inLen = strlen( szInName );
    if  ( inLen < 4  ||  ( szInName[ inLen ] != '.'  &&
            strcmp( szInName + inLen - 4, ".tex" ) != 0 ) ) {
        strcat( szInName, ".tex" );
    }

    printf( "input file: [%s]\n", szInName );

    strcpy( pFlags->szFullName, szInName );

    strcpy( szOutName, szInName );
    replaceExt( szOutName, ".flt" );

    flOut = fopen( szOutName, "wt" );
    if   ( flOut == NULL ) {
        fprintf( stderr, "Unable to open file: %s", szOutName );
        exit( -1 );
    }

    flatIt( flOut, szInName, 0, pFlags );

    fclose( flOut );
    
    printf( "\n\tFile: \"%s\" generated\n", szOutName );
}


static char    isFlag( char     * str, char       ch )
{
    if  ( str[ 0 ] == '-'  &&
          ( str[ 1 ] == ch  || str[ 1 ] == toupper( ch ) )
          &&  ( str[ 2 ] == 0 ) )
      return  TRUE;

    return  FALSE;
}


int       main( int    argc, char   * argv[] )
{
    int                   ind;
    structFlags           sFlags;

    printf( "FLATEX  1.21, 1994, 1996, by Sariel Har-Peled.\n\n" );
    if   ( argc == 1 )
        printHelp();

    sFlags.verbose = FALSE;
    sFlags.fBibInsert = TRUE;
    sFlags.cSpecialInputLevel = 0;
    *sFlags.szFullName = 0;
    sFlags.fQuiet = FALSE;

    for   ( ind = 1; ind < argc; ind++ ) {
        if   ( isFlag( argv[ ind ], 'v' ) ) {
            sFlags.verbose = TRUE;
            continue;
        }
        if   ( isFlag( argv[ ind ], 'b' ) ) {
            sFlags.fBibInsert = FALSE;
            continue;
        }
        if   ( isFlag( argv[ ind ], 'q' ) ) {
            sFlags.fQuiet = TRUE;
            continue;
        }
        if   ( isFlag( argv[ ind ], 'x' ) ) {
            flatOutFile( argv[ ind + 1 ], &sFlags );
            ind++;
            continue;
        }

        flatFile( argv[ ind ], &sFlags );
    }
    return   0;
}

/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
 *
 * flatex.c - End of File
\*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/