/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    applyinf.cxx

Abstract:

    Merge HTML documents & localizable string .inf file

Author:

    Philippe Choquier ( Phillich ) 15-may-1996

--*/

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

#include    <iis64.h>

typedef struct _SUBST_NODE {
    LPSTR pszName;
    LPSTR pszValue;
} SUBST_NODE;

#define MAX_NODES       8192
#define MAX_SIZE_NAME   256
#define MAX_SIZE_VALUE  8192

#define INF_SEEK_FIRST_CHAR_NAME    0
#define INF_SEEK_NAME               1
#define INF_SEEK_END_NAME           2
#define INF_SEEK_STR                3
#define INF_SEEK_END_STR            4
#define INF_SEEK_EOL                5

#define HT_SEEK_NAME                0
#define HT_SEEK_END_NAME            1

SUBST_NODE aNodes[MAX_NODES];
int cNodes = 0;

char achName[MAX_SIZE_NAME];
char achValue[MAX_SIZE_VALUE];


extern "C" int __cdecl
QsortStrCmp(
    const void *pA,
    const void *pB )
/*++

Routine Description:

    Compare two SUBST_NODE structures base on their pszName field

Arguments:

    pA - ptr to 1st struct
    pB - ptr to 2nd struct

Returns:

    -1 if *pA < *pB, 0 if *pA == *pB, 1 if *pA > *pB
    based on strcmp of their pszName field

--*/
{
    return strcmp( ((SUBST_NODE*)pA)->pszName, ((SUBST_NODE*)pB)->pszName );
}



BOOL
ParseINF(
    LPSTR pszInf
    )
/*++

Routine Description:

    Parse the .inf file for localizable string substitutions

Arguments:

    pszInf - name of .inf file

Returns:

    TRUE if success, FALSE if error

--*/
{
    BOOL fSt = TRUE;
    int ch;
    FILE *inf;

    if ( ( inf = fopen(pszInf, "r") ) == NULL )
    {
        return FALSE;
    }

    int iName = 0;
    int iValue = 0;
    BOOL fEsc;

    int iState = INF_SEEK_FIRST_CHAR_NAME;

    for ( ; (ch = fgetc( inf )) != EOF ; )
    {
        if ( ch == '\\' )
        {
            if ( (ch = fgetc( inf )) == EOF )
            {
                break;
            }
            if ( ch == '\n' )
            {
                continue;
            }
            if ( ch == 'n' )
            {
                ch = '\n';
            }
            fEsc = TRUE;
        }
        else
        {
            fEsc = FALSE;
        }

        switch ( iState )
        {
            case INF_SEEK_FIRST_CHAR_NAME:
                if ( !fEsc && ch == '#' )
                {
                    iState = INF_SEEK_EOL;
                    break;
                }
                iState = INF_SEEK_NAME;
                // fall-through

            case INF_SEEK_NAME:
                if ( !fEsc && ch == '^' )
                {
                    iState = INF_SEEK_END_NAME;
                }
                break;

            case INF_SEEK_END_NAME:
                if ( !fEsc && ch == '^' )
                {
                    iState = INF_SEEK_STR;
                }
                else
                {
                    achName[ iName++ ] = (char)ch;
                }
                break;

            case INF_SEEK_STR:
                if ( !fEsc && ch == '"' )
                {
                    iState = INF_SEEK_END_STR;
                }
                break;

            case INF_SEEK_END_STR:

                // handle "" as a single quote

                if ( !fEsc && ch == '"' )
                {
                    if ( (ch = fgetc( inf )) == EOF )
                    {
                        break;
                    }
                    if ( ch == '"' )
                    {
                        fEsc = TRUE;
                    }
                    else
                    {
                        ungetc( ch, inf );
                        ch = '"';
                    }
                }

                // skip new lines char in stream

                if ( !fEsc && ch == '\n' )
                {
                    break;
                }

                if ( !fEsc && ch == '"' )
                {
                    achName[ iName ] = '\0';
                    achValue[ iValue ] = '\0';
                    aNodes[ cNodes ].pszName = _strdup( achName );
                    aNodes[ cNodes ].pszValue = _strdup( achValue );
                    ++cNodes;
                    iState = INF_SEEK_FIRST_CHAR_NAME;
                    iName = 0;
                    iValue = 0;
                }
                else
                {
                    achValue[ iValue++ ] = (char)ch;
                }
                break;

            case INF_SEEK_EOL:
                if ( !fEsc && ch == '\n' )
                {
                    iState = INF_SEEK_FIRST_CHAR_NAME;
                }
                break;
        }
    }

    qsort( aNodes, cNodes, sizeof(SUBST_NODE), QsortStrCmp );

    fclose( inf );

    return fSt;
}


BOOL
ParseHTML(
    LPSTR pszIn,
    LPSTR pszOut
    )
/*++

Routine Description:

    Parse a HTML document and generate an output document
    based on a previously parsed .inf susbtitution file

Arguments:

    pszIn - name of input HTML document
    pszOut - name of created localized HTML document

Returns:

    TRUE if success, FALSE if error

--*/
{
    BOOL fSt = TRUE;
    int ch;
    FILE *in;
    FILE *out;

    if ( ( in = fopen(pszIn, "r") ) == NULL )
    {
        return FALSE;
    }
    if ( ( out = fopen(pszOut, "w") ) == NULL )
    {
        fclose( in );
        return FALSE;
    }

    int iName = 0;
    BOOL fEsc;

    int iState = HT_SEEK_NAME;

    for ( ; (ch = fgetc( in )) != EOF ; )
    {
        if ( ch == '\\' )
        {
            if ( (ch = fgetc( in )) == EOF )
            {
                break;
            }
            if ( ch == '\n' )
            {
                continue;
            }
            fEsc = TRUE;
        }
        else
        {
            fEsc = FALSE;
        }

        switch ( iState )
        {
            case HT_SEEK_NAME:
                if ( !fEsc && ch == '^' )
                {
                    iState = HT_SEEK_END_NAME;
                }
                else
                {
                    fputc( ch, out );
                }
                break;

            case HT_SEEK_END_NAME:
                if ( !fEsc && ch == '^' )
                {
                    SUBST_NODE snSeek;
                    SUBST_NODE *pN;
                    snSeek.pszName = achName;
                    achName[ iName ] = '\0';

                    if ( (pN = (SUBST_NODE*)bsearch( &snSeek,
                            aNodes,
                            cNodes,
                            sizeof(SUBST_NODE),
                            QsortStrCmp ))
                            != NULL )
                    {
                        fputs( pN->pszValue, out );
                    }
                    else
                    {
                        fprintf( stdout, "Can't find reference to %s in %s\n",
                            achName,
                            pszIn );
                        fflush( stdout );
                    }
                    iState = HT_SEEK_NAME;
                    iName = 0;
                }
                else
                {
                    achName[ iName++ ] = (char)ch;
                }
                break;
        }
    }

    fclose( in );
    fclose( out );

    return fSt;
}


BOOL
BreakPath(
    LPSTR pOut,
    LPSTR pExt,
    BOOL *pfIsExt
    )
/*++

Routine Description:

    Move a file extension from a file path to an extension buffer

Arguments:

    pOut - file path updated to remove file extension
    pExt - buffer for file extension
    pfIsExt - set to TRUE if file extension present

Returns:

    TRUE if success, FALSE if error

--*/
{
    // if ends with '\\' is directory
    // else extract extension

    LPSTR pL = pOut + strlen(pOut);
    LPSTR pE = NULL;

    if ( pL[-1] == '\\' )
    {
        *pfIsExt = FALSE;
        return TRUE;
    }

    while ( pL > pOut && pL[-1] != '\\' )
    {
        if ( pL[-1] == '.' && pE == NULL )
        {
            pE = pL;
        }
        --pL;
    }

    if ( pL == pOut )
    {
        return FALSE;
    }

    *pL = '\0';
    strcpy( pExt, pE );
    *pfIsExt = TRUE;

    return TRUE;
}


void
Usage(
    )
/*++

Routine Description:

    Display usage for this utility

Arguments:

    None

Returns:

    Nothing

--*/
{
    fprintf( stdout,
"\n"
"Usage: applyinf [source_file] [target_directory] [inf_file]\n"
"       source_file : can contains wild card characters\n"
"       target_directory : can contains a new extension to be\n"
"                          used, e.g. *.out\n"
"       inf_file : name of the .inf files containing replacement strings\n"
"\n" );
}


BOOL
Combine(
    LPSTR pOut,
    LPSTR pExt,
    BOOL fIsExt,
    LPSTR pFileName
    )
/*++

Routine Description:

    Combine file name & extension to a new file name

Arguments:

    pOut - output filename
    pExt - contains file extension if fIsExt is TRUE
    fIsExt - TRUE if pExt contains file extension
    pFileName - filename to be combined with extension to generare pOut

Returns:

    TRUE if success, FALSE if error

--*/
{
    LPSTR pL = pFileName + strlen(pFileName);


    if ( fIsExt )
    {
        while ( pL > pFileName && pL[-1] != '.' )
            --pL;

        if ( pL == pFileName )
        {
            // no ext in filename
            memcpy( pOut, pFileName, strlen(pFileName) );
            pOut += strlen(pFileName );
        }
        else
        {
            memcpy( pOut, pFileName, DIFF(pL - pFileName) - 1 );
            pOut += pL - pFileName - 1;
        }
        *pOut ++ = '.';
        strcpy( pOut, pExt );
    }
    else
    {
        strcpy( pOut, pFileName );
    }

    return TRUE;
}


int __cdecl
main(
    int argc,
    char *argv[]
    )
/*++

Routine Description:

    Entry point of this utility, parse command line

Arguments:

    argc - nbr of command line parameters
    argv - ptr to command line parameters

Returns:

    0 if success, else error code

--*/
{
    char achIn[MAX_PATH]="";
    char achOut[MAX_PATH]="";
    char achInf[MAX_PATH]="";
    char achExt[MAX_PATH];
    BOOL fIsExt;
    WIN32_FIND_DATA fdIn;
    HANDLE hF;
    int arg;
    int iN = 0;
    LPSTR pLastS;
    LPSTR pOut;

    for ( arg = 1 ; arg < argc ; ++arg )
    {
        if ( argv[arg][0] == '-' )
        {
            switch( argv[arg][1] )
            {
                case 'z':
                default:
                    ;
            }
        }
        else
        {
            switch ( iN )
            {
                case 0:
                    strcpy( achIn, argv[arg] );
                    break;

                case 1:
                    strcpy( achOut, argv[arg] );
                    break;

                case 2:
                    strcpy( achInf, argv[arg] );
                    break;
            }
            ++iN;
        }
    }

    if ( achIn[0] == '\0' )
    {
        fprintf( stdout, "No source directory specified\n" );
        fflush( stdout );
        Usage();
        return 3;
    }

    if ( achOut[0] == '\0' )
    {
        fprintf( stdout, "No target directory specified\n" );
        fflush( stdout );
        Usage();
        return 3;
    }

    if ( achInf[0] == '\0' )
    {
        fprintf( stdout, "No INF file specified\n" );
        fflush( stdout );
        Usage();
        return 3;
    }

    for ( pLastS = achIn + strlen(achIn) ; pLastS > achIn ; --pLastS )
    {
        if ( pLastS[-1] == '\\' )
        {
            break;
        }
    }

    if ( pLastS == achIn )
    {
        fprintf( stdout, "Invalid source directory : %s\n", achIn );
        fflush( stdout );
        return 5;
    }

    if ( !BreakPath( achOut, achExt, &fIsExt ) )
    {
        fprintf( stdout, "Invalid target directory : %s\n", achOut );
        fflush( stdout );
        return 6;
    }
    pOut = achOut + strlen( achOut );

    if ( !ParseINF( achInf ) )
    {
        fprintf( stdout, "Can't parse INF file %s\n", achInf );
        fflush( stdout );
        return 1;
    }

    // applyinf srcdir trgdirandext inffile
    // e.g. applyinf c:\nt\*.htr c:\drop\*.htm html.inf

    if ( (hF = FindFirstFile( achIn, &fdIn )) != INVALID_HANDLE_VALUE )
    {
        do {
            strcpy( pLastS, fdIn.cFileName );
            Combine( pOut, achExt, fIsExt, fdIn.cFileName );

            if ( !ParseHTML( achIn, achOut) )
            {
                fprintf( stdout, "Can't generate %s from %s\n", achOut, achIn );
                fflush( stdout );
                return 2;
            }
            else
            {
                fprintf( stdout, "Parsed %s to %s\n", achIn, achOut );
                fflush( stdout );
            }
        } while ( FindNextFile( hF, &fdIn ) );

        FindClose( hF );
    }
    else
    {
        fprintf( stdout, "No file found in %s", achIn );
        fflush( stdout );
        return 4;
    }

    return 0;
}