#!/usr/bin/perl -w

######################################################################
#
#  srchtml2html.pl
#
#
#  Usage:
#      srchtml2html.pl -l lang [options] [-o dstfile.html] srcfile.srchtml
#
#  Options:
#      -b basename  base file name for langswitch & destfilename guess
#      -p publdir
#      -s sitedir
#      -i imagdir
#      -I incldir   can be specified multiple times
#      -x idxfile
#
######################################################################

use strict;
require 5.003;

#### Variables #######################################################

my $infile           = undef;
my $outfile          = undef;
my $destfile         = undef;
my $idxfile          = undef;
my $basename         = undef;
my $basedir          = "";
my $publdir          = undef;
my $sitedir          = undef;
my $imagdir          = undef;
my @incldirs         = ();

my $pp_only          = 0;
my $pp_defines       = 0;
my $pp_defines_shell = 0;
my $pp_defines_perl  = 0;
my $pp_defslist      = undef;
my $pp_includes      = 0;
my $pp_includes_all  = 0;

my $thisfile         = undef;
my $thisline         = undef;
my $include_depth    = 0;

my %sources_used     = ();
my %configs_used     = ();
my $srcfilesusedref  = 0; # Not undef!

my $q_year           = undef; # ^!
my $q_lsuffix        = undef; # ^?
my $q_publdir        = undef; # ^|
my $q_sitedir        = undef; # ^/
my $q_imagdir        = undef; # ^=

my $line             = undef;
my $curlang          = "*";
my $verbatim         = 0;
my $verbatim_start   = undef;
my $multiline        = 0;
my $multiline_start  = undef;

my $reading_config   = 0;
my $strip_emptylines = 0;
my $in_sysinclude    = 0;

my %defines          = ();
my %defines_nargs    = ();
my %defines_origin   = ();

my $page_charset       = undef;
my $page_defcharset    = undef;
my $page_description   = undef;
my $page_keywords      = undef;
my $page_title         = undef;
my $page_appearance    = undef;
my $page_defappearance = undef;
my $page_logo          = undef;
my $page_heading       = undef;
my $page_content       = "";

my $page_isframeset    = undef;
my $page_isssi         = undef;

my $option_lang   = undef;
my @langs         = ();
my $langswitch_required;
my $langswitchpos = undef;
my @langsvisible  = ();

my $hack_imagdir = undef;

my %avldirvs = (
                 "charset"       => \$page_charset,
                 "defcharset"    => \$page_defcharset,
                 "description"   => \$page_description,
                 "keywords"      => \$page_keywords,
                 "title"         => \$page_title,
                 "appearance"    => \$page_appearance,
                 "defappearance" => \$page_defappearance,
                 "logo"          => \$page_logo,
                 "heading"       => \$page_heading,

                 "hack_imagdir"  => \$hack_imagdir
               );
my %dirvorg = ();

my $index_labelctr = 1;
my $index_content  = "";

#### Utilities #######################################################

sub META_XXX($$)
{
  my $name    = $_[0];
  my $content = $_[1];
    
    return "<META NAME=\"$name\" CONTENT=\"$content\">";
}

sub META_GENERATOR()
{
  my $progname = `basename $0`;
  my $uname_s  = `uname -s`;

    chomp($progname);
    chomp($uname_s);

    return META_XXX("GENERATOR", "$progname ($uname_s)");
}

sub LANG_SWITCH()
{
  my $result;
  my $l;
  my $ref;

    $result = "<!-- Begin langswitch -->\n<TABLE ALIGN=right>\n<TR>\n";

    foreach $l (@langsvisible)
    {
        $result .= "<TD>";

        if ($l eq $option_lang)
        {
            $result .= "<IMG SRC=\"${imagdir}lnf_$l.gif\" ALT=\"$l\" BORDER=0>";
        }
        else
        {
            $ref = $basename;
            $result .= "<A HREF=\"$ref".".$l.html\">";
            $result .= "<IMG SRC=\"${imagdir}lng_$l.gif\" ALT=\"$l\" BORDER=0>";
            $result .= "</A>";
        }
        
        $result .= "</TD>\n";
    }

    $result .= "</TR>\n</TABLE>\n<!-- End langswitch -->\n";

    return $result;
}

#=== TagFlags ========================================================
# Returns the Arg0 preceded with ' ' if Arg0 is not a null-string,
# "" otherwise, thus making <TAG ARGS> or <TAG> (not a <TAG >).
sub TagFlags($)
{
    return ($_[0] ne ""? " " : "") . $_[0];
}

#=== PosInList =======================================================
#  Returns the 0-based position of Arg0 in list @_[1..], 
#              -1 if not found.
#  Used to determine if Arg1 is present in list
sub PosInList($@)
{
  my $element;

    for($element=0; $element<$#_; $element++)
    {
        return $element if $_[$element+1] eq $_[0];
    }

    return -1;
}


sub error(@)
{
  my $word;

    print STDERR "$thisfile:$thisline:";
    foreach $word (@_)
    {
        print STDERR " $word";
    }

    die "\n";
}

sub syntaxerror()
{
    error "syntax error";
}

sub uxeol()
{
    error "unexpected end of line";
}

sub warning(@)
{
  my $word;

    print STDERR "$thisfile:$thisline: WARNING:";
    foreach $word (@_)
    {
        print STDERR " $word";
    }

    print "\n";
}


#### Subroutines #####################################################

sub ParseCmdLine()
{
  my $x = 0;  # Index in ARGV
  my $a;      # Arg
  my $o;      # Operand
  my $p;      # Position in string

    # Prepare an exitcode for die()
    $! = 1;
  
    # First, parse the command line
    while ($x <= $#ARGV)
    {
        $a = $ARGV[$x++];

        # Well, what is it?
        
        # Gcc-style preprocessor option?
        if    ($a eq "-M")
        {
            $pp_only          = 1;
            $pp_includes      = 1;
            $pp_includes_all  = 1;
        }
        elsif ($a eq "-MM")
        {
            $pp_only          = 1;
            $pp_includes      = 1;
            $pp_includes_all  = 0;
        }
        elsif ($a eq "-dM")
        {
            $pp_only          = 1;
            $pp_defines       = 1;
            $pp_defines_shell = 0;
            $pp_defines_perl  = 0;
        }
        elsif ($a eq "-dS")
        {
            $pp_only          = 1;
            $pp_defines       = 1;
            $pp_defines_shell = 1;
            $pp_defines_perl  = 0;
        }
        elsif ($a eq "-dP")
        {
            $pp_only          = 1;
            $pp_defines       = 1;
            $pp_defines_shell = 0;
            $pp_defines_perl  = 1;
        }
        elsif ($a eq "-dONLY")
        {
            die "$0: $a requires an operand (comma-separated list of names)\n"
                if $x > $#ARGV;
            $pp_defslist = $ARGV[$x++];
        }
        # Normal option?
        elsif ($a =~ /^-/)
        {
            die "$0: invalid option -- $a\n"
                if ($a !~ /^-[lobpsiIx]$/);
            die "$0: missing operand to $a\n"
                if $x > $#ARGV;
            $o = $ARGV[$x++];

            if    ($a eq "-l")
            {
                die "$0: multiple `-l lang' specification\n"
                    if defined($option_lang);
                die "$0: invalid language $o\n"
                    if $o eq ""  ||  $o =~ /\W/;
                $option_lang = $o;
            }
            elsif ($a eq "-o")
            {
                die "$0: multiple output file specification\n"
                    if defined($outfile);
                $outfile = $o;
            }
            elsif ($a eq "-b")
            {
                die "$0: multiple '-b' options\n"
                    if defined($basename);
                $basename = $o;
            }
            elsif ($a eq "-p")
            {
                die "$0: multiple '-p' options\n"
                    if defined($publdir);
                $publdir = $o;
            }
            elsif ($a eq "-s")
            {
                die "$0: multiple '-s' options\n"
                    if defined($sitedir);
                $sitedir = $o;
            }
            elsif ($a eq "-i")
            {
                die "$0: multiple '-i' options\n"
                    if defined($imagdir);
                $imagdir = $o;
            }
            elsif ($a eq "-I")
            {
                push @incldirs, $o;
            }
            elsif ($a eq "-x")
            {
                die "$0: multiple index file specification\n"
                    if defined($idxfile);
                $idxfile = $o;
            }
            else
            {
                die "$0: unknown option '$a'\n";
            }
        }
        # No, filename
        else
        {
            die "$0: multiple source file specification\n"
                if defined($infile);
            $infile = $a;
        }
    }


    # Second, check that we aren't going to read from terminal
    if (!defined($infile))
    {
        $infile = "-"; #die "$0: no input file specified\n";
    }
    
    if ($infile eq "-"  &&  -t STDIN)
    {
        die "Usage: $0 ...\n";
    }

    
    # Third, autoset omitted parameters...

    # No language -- just preprocess
    if (!defined($option_lang))
    {
        $option_lang = "*";
    }

    # No base file name?
    if (!defined($basename))
    {
        if ($infile =~ /^(.*?)\.srchtml$/  ||  $infile =~ /^(.*?)\.review$/)
        {
            $basename = $1;
            $p = rindex($basename, '/');
            if ($p >= 0)
            {
                $basedir  = substr($basename, 0, $p + 1);
                $basename = substr($basename, $p + 1);
            }
        }
        else
        {
            print STDERR "$0: WARNING: non-.srchtml source file name. Unable to derive base/destination file name properly (setting basename to \"$infile\").\n"
                if $option_lang ne "*"  ||  !defined($outfile);
            $basename = $infile;
        }

    }

    # Derive destination .html-file name
    $destfile = "";
    if ($basename ne "-")
    {
        $destfile  = $basedir . $basename;
        $destfile .= ".$option_lang" if $option_lang ne "*";
        $destfile .= ".html";
    }

    # Derive the index file name
    #if (!defined($idxfile))
    #{
    #    $idxfile = "/dev/null";
    #    if ($destfile ne "")
    #    {
    #        $idxfile = "$destfile.sidx";
    #    }
    #}
    
    # Infile or basename specified while outfile isn't? Derive it...
    if (!defined($outfile))
    {
        $outfile = !$pp_only ?  $destfile
                             :  "-";
    }
    
    ## Infile specified while outfile isn't? Derive it...
    #if (!defined($outfile))
    #{
    #    $outfile = $basedir . $basename;
    #    $outfile .= ".$option_lang" if $option_lang ne "*";
    #    $outfile .= ".html";
    #
    #    if ($pp_only)
    #    {
    #        $outfile = "-";
    #    }
    #}

    # No publdir specified?
    if (!defined($publdir))
    {
        $publdir = "";
        if (exists($ENV{'PUBLDIR'}))
        {
            $publdir  = $ENV{'PUBLDIR'};
            $publdir .= "/" if $publdir ne ""  &&  $publdir !~ /\/$/;
        }
        $publdir = "" if $publdir eq "./";
    }

    # No sitedir specified?
    if (!defined($sitedir))
    {
        $sitedir = "";
        if (exists($ENV{'SITEDIR'}))
        {
            $sitedir  = $ENV{'SITEDIR'};
            $sitedir .= "/" if $sitedir ne ""  &&  $sitedir !~ /\/$/;
        }
        $sitedir = "" if $sitedir eq "./";
    }

    # No imagdir specified?
    if (!defined($imagdir))
    {
        $imagdir = "";
        if (exists($ENV{'IMAGDIR'}))
        {
            $imagdir  = $ENV{'IMAGDIR'};
            $imagdir .= "/" if $imagdir ne ""  &&  $imagdir !~ /\/$/;
        }
        else
        {
            $imagdir = $sitedir . "i/";
        }
        
        $imagdir = "" if $imagdir eq "./";
    }
}

sub SetPredefined()
{
  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
    
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
                         
    $q_year    = 1900 + $year; #!!! BLIN!!!!!!!!!!!!!!

    $q_lsuffix = "";
    $q_lsuffix = ".$option_lang" if $option_lang ne "*";

    $q_publdir = $publdir;
    $q_sitedir = $sitedir;
    $q_imagdir = $imagdir;
}

sub ExpandMacro($$$)
{
  my $result = "";
  my $name   = $_[0];
  my $pars   = $_[1];
  my $sep    = quotemeta($_[2]);
  my @args   = split(/\s*$sep\s*/, $pars);
  my $value  = $defines{$name};
  my $max    = $defines_nargs{$name};
  my $origin = "macro \"$name\" (defined at $defines_origin{$name})";

  my $pos    = 0;
  my $len    = length($value);
  my $ch;

    #print STDERR "ExpandMacro:\n\tname='$name'\n\tvalue='$value'\n\tpars='$pars'\n";

    error "too many arguments (".($#args+1).") for macro \"$name\" (it allows $max max)"
        if $#args + 1 > $max;
    
    SCAN: while ($pos < $len)
    {
        $ch = substr($value, $pos++, 1);
        
        if ($ch eq "\$")
        {
            error "'\$' at end of macro definition in $origin" if $pos >= $len;
            $ch = substr($value, $pos++, 1);

            # Just a dollar sign?
            if    ($ch eq "\$")
            {
                $result .= $ch;
            }
            #!!! A temp hack -- "$_"=="^_"=="&nbsp;"
            elsif ($ch eq "_")
            {
                $result .= "&nbsp;";
            }
            # Parameter reference?
            elsif ($ch =~ /[1-9]/)
            {
                # Is it in range?
                if ($ch > $max)
                {
                    error "parameter \"\$$ch\" referenced in parameterless $origin"
                        if $max == 0;
                    error "parameter number '$ch' is out of range [1..$max] in $origin";
                }

                $result .= $args[$ch - 1] if $#args >= $ch - 1;
            }
            else
            {
                error "unknown parameter '\$$ch' in $origin";
            }
        }
        else
        {
            $result .= $ch;
        }
    }

    return $result;
}

sub PreprocessLine()
{
  my $result  = "";
  my $pos     = 0;
  my $linelen = length($line);
  my $carpos;
  my $ch;
  my $optional;
  my $sep;
  my $clip;

    SCAN: while ($pos < $linelen)
    {
        $carpos = index($line, '^', $pos);
        if ($carpos < 0)
        {
            $result .= substr($line, $pos);
            last SCAN;
        }

        $result .= substr($line, $pos, $carpos - $pos);
        $pos = $carpos + 1;

        error "'^' at end of line" if $pos >= $linelen;
        $ch = substr($line, $pos++, 1);

        if    ($ch eq '^'  ||  $ch eq "#")
        {
            $result .= $ch;
            next SCAN;
        }
        elsif ($ch eq '_')
        {
            $result .= "&nbsp;";
            next SCAN;
        }
        elsif ($ch eq '(')
        {
            # A big fat note:
            #     1. This code should be changed to allow nested macro calls,
            #     such as
            #         ^(macro1 abc, def^(macro2)ghj, xyz)
            #     and
            #         .$define macro1(1)=abc^(macro2 $1)def
            #     (in fact, the latter one should work right now!)
            #     and even
            #         ^(^(macroname) abc)
            #     But '-', ',' and '|' should have special meaning only if
            #     specified explicitly, but not if they come from a nested
            #     macro expansion.
            #
            #     2. It should allow line break inside a macro, such as
            #         ^(macro abc\ndef)
            #
            #     3. Additionally to ^(macro), the following escapes should
            #     be recognized:
            #
            #         ^{name:-VALUE} results in VALUE if name is unset or null,
            #             otherwise in ^(name)  // Use default value
            #
            #         ^{name:+VALUE} results in VALUE if name is set and non-null,
            #             otherwise in nothing  // If-Then
            #
            #         ^{name?-VALUE_IF_MISSING,VALUE_IF_EXISTS} "IfNot-Then-Else"
            #         ^{name?+VALUE_IF_EXISTS,VALUE_IF_NOT}     "If-Then-Else"
            #             these two accept the '|' modifier (i.e. ^{|name...})
            #             for a ^{|name?V1|V2} form
            #
            #         ^<command data> should perform compiler-dependent actions
            #
            #         ^[expr] is reserved for arithmetic expressions
            #
            #     4. Inside macros, the $c, $(...), ${...} and $<...> escapes
            #     should be supported.  Their action is the same as that of
            #     ^..., but expansion is performed at the time of macro
            #     expansion, with macro parameters $1...$9 being just aliases
            #     to $(1)...$(9)
            #     (of course, ^1..^9 and ^(1)...^(9) are senseless).
            #
            #     5. .$define/.$definexline should accept the following syntax:
            #         .$define macro(2+)=....
            #     which means that $2 swallows everything up to ')', treating
            #     ','s and '|'s as usual chars, so that
            #         .$define macro(2+)=arg1:"$1", arg2:"$2"
            #         ^(macro abc, def, ghij,    klmn)
            #     will result in
            #         arg1:"abc", arg2:"def, ghij,    klmn"
            #
            # Functions:
            #     AppendNextLine($location);
            #     ExpandEscape:
            #         1. From $line:$pos ('^'):
            #            expand all nested '^'s, 
            #         2. From macro body ('$'):
            #            expand all nested '$'s, treate '^' as usual chars
            #

            uxeol if $pos >= $linelen;
            $optional = 0;
            $sep      = ',';

            $ch = substr($line, $pos, 1);
            if ($ch eq '-')
            {
                $pos++;
                $optional = 1;
            }

            $ch = substr($line, $pos, 1);
            if ($ch eq '|')
            {
                $pos++;
                $sep = $ch;
            }
            
            uxeol if $pos >= $linelen;
            $clip = substr($line, $pos);

            $clip =~ /^(\.?\w+)(:|\s*)(.*?)\)/  ||  error "syntax error (after position $pos)";
            if (substr($1, 0, 1) eq ".")
            {
                if ($1 eq ".uc")
                {
                    $result .= uc($3);
                }
                elsif ($1 eq ".lc")
                {
                    $result .= lc($3);
                }
                else
                {
                    error "unknown builtin-function \"$1\"";
                }
            }
            elsif (exists($defines{$1}))
            {
                #$result .= $defines{$1};
                $result .= ExpandMacro($1, $3, $sep);
            }
            else
            {
                error "\"$1\" undefined" if !$optional;
            }

            $clip =~ /^(.*?\))/;
            $pos += length($1);
        }
        elsif ($ch eq '?')
        {
            $result .= $q_lsuffix;
        }
        elsif ($ch eq '|')
        {
            $result .= $q_publdir;
        }
        elsif ($ch eq '/')
        {
            $result .= $q_sitedir;
        }
        elsif ($ch eq '=')
        {
            $result .= $q_imagdir;
        }
        elsif ($ch eq '!')
        {
            $result .= $q_year;
        }
        else
        {
            error "unknown escape '^$ch' (at column $pos)";
        }
    }

    $line = $result;
}

sub ReadOneFile($)
{
  my    $filename = $_[0];
  local *SRC;
  my    @dirlist;
  my    $fullname;
  my    $upfile   = $thisfile;
  my    $upline   = $thisline;
  my    $active;
  my    $dirv;
  my    $args;
  my    $x;
  my    $xlineref;

    @dirlist = ("");
    push @dirlist, @incldirs if $include_depth != 0;

    FINDFILE: for ($x = 0; $x <= $#dirlist; $x++)
    {
        $fullname = $dirlist[$x] ne ""? "$dirlist[$x]/$filename" : $filename;
        if (!open(SRC, "<$fullname"))
        {
            next FINDFILE if $x < $#dirlist;
            
            $include_depth == 0
                ? die "$0: unable to open \"$filename\" for input -- $!\n"
                : error "unable to open \"$filename\" for input -- $!\n";
        }
        else
        {
            last FINDFILE;
        }
    }

    $thisfile = $fullname;
    $thisline = 0;

    ${$srcfilesusedref}{$filename} = 1 if $filename ne "-";

    # ((((
    READLINE: while(<SRC>)
    {
        chomp;
        $thisline++;
        
        $line = $_;

        $active = ($curlang eq "*") || ($curlang eq $option_lang);

        #### Phase 1 #################################################
        #  Line is unchanged, so can be taken "as-is", if required
        ##############################################################
        
        # Is in verbatim mode?
        if ($verbatim)
        {
            if ($line =~ /^.\$endverbatim\s*$/)
            {
                $verbatim = 0;
                next READLINE;
            }
            else
            {
                $page_content .= $line . "\n" if $active;
                next READLINE;
            }
        }

        #  ".$verbatim" should be processed even in different language,
        #  to make constructs like
        #      .=en
        #      .$verbatim
        #      .=ru
        #      .$endverbatim
        #  valid when "-l ru" is in effect
        if    ($line =~ /^.\$\s*verbatim/)
        {
            $verbatim = 1;
            $verbatim_start = $thisline;
            next READLINE;
        }

        
        #### Phase 2 #################################################
        #  Strip out comments and do language separation
        ##############################################################
        
        PHASE2:{
        # Comment?
        if    ($line =~ /^\#/)
        {
            next READLINE;
        }
        # Language change?
        elsif ($line =~ /^\.=/)
        {
            # "all languages"?
            if ($line =~ /^\.=\s*\*/)
            {
                $curlang = "*";
                next READLINE;
            }
            
            error "'.=' without prior \$langs"
                if $#langs < 0  &&  !$reading_config;
            $line =~ /^\.\=\s*(\w+)$/ || syntaxerror();
            $curlang = $1;
            error "unknown language `$curlang'"
                if PosInList($curlang, @langs) < 0  &&  !($reading_config || $in_sysinclude);
            next READLINE;
        }
        # Language change for one line?
        elsif ($line =~ /^\.\(/)
        {
            # "all languages"?
            if ($line =~ /^\.\(\*\)/)
            {
                $args = "*";
            }
            else
            {
                error "'.(' without prior \$langs"
                    if $#langs < 0  &&  !$reading_config;
                $line =~ /^\.\((\w+)\)/ || syntaxerror();
                $args = $1;
                error "unknown language `$args'"
                    if PosInList($args, @langs) < 0  &&  !($reading_config || $in_sysinclude);
            }

            # Cut the ".(lang)" directive and whitespace after it
            $line =~ s<^\.\(.*?\)\s*(.*?)\s*$><$1>;

            $active = ($args eq "*") || ($args eq $option_lang);
        }
        # Single-line conditional?
        elsif ($line =~ /^\.\?/)
        {
          my $satisfied = undef;
            
            $line =~ /^\.\?(\w+)/ || syntaxerror();
            $dirv = $1;
            if ($dirv eq "ifdef"  ||  $dirv eq "ifndef")
            {
                $line =~ /^\.\?\w+\s+(\w+)\s*:/ || syntaxerror();
                $satisfied = ($dirv eq "ifndef") ^ defined($defines{$1});
                $line =~ s<^.*?:\s*(.*?)\s*$><$1>;
            }
            else
            {
                error "unknown single-line conditional `$dirv'";
            }

            next READLINE if !$satisfied;
            redo PHASE2;
        }
        }
        
        next READLINE if !$active;

        
        #### Phase 3 #################################################
        #  Expand all those ^...s
        ##############################################################
        PreprocessLine();

        
        #### Phase 4 #################################################
        #  Preprocessed line is available for multiline directives
        ##############################################################
        
        # Is inside .$definexline?
        if ($multiline)
        {
            if ($line =~ /^.\$endxline\s*$/)
            {
                chomp(${$xlineref}); # Remove last "\n"
                $multiline = 0;
                next READLINE;
            }
            else
            {
                ${$xlineref} .= $line . "\n";
                next READLINE;
            }
        }

        #### Phase 5 #################################################
        #  Strip empty lines if .$strip_emptylines was requested
        ##############################################################
        next READLINE if $line eq ""  &&  ($strip_emptylines || $in_sysinclude);
        
        #### Phase 6 #################################################
        #  Process '.'-directives
        ##############################################################
        
        # $directive ?
        if    ($line =~ /^\.\$/)
        {
            $line =~ /^\.\$\s*(\w+)(.*)$/s  ||  syntaxerror();
            $dirv = $1; 
            $args = $2; $args =~ s<^\s*(.*?)\s*$><$1>s;

            if    ($dirv eq "endfile")
            {
                last READLINE;
            }
            elsif ($dirv eq "langs")
            {
                error "duplicate \".\$langs\"" if $#langs >= 0;
                @langs = split(/\s*,\s*/, $args);
                error "empty languages list" if $#langs < 0;
                foreach $x (@langs)
                {
                    error "invalid language name \"$x\"" if $x =~ /\W/;
                }

                # Should check the '-l' switch
                error "the language \"$option_lang\" missing (requested at command line)"
                    if $option_lang ne "*"  &&  PosInList($option_lang, @langs) < 0;
            }
            elsif (exists($avldirvs{$dirv}))
            {
                error "duplicate \".\$$dirv\" (used at $dirvorg{$dirv})" if defined(${$avldirvs{$dirv}});
                ${$avldirvs{$dirv}} = $args;
                $dirvorg{$dirv}     = "$thisfile:$thisline";
            }
            elsif ($dirv eq "langswitch")
            {
                $langswitch_required = 1;
            }
            elsif ($dirv eq "langswitchhere")
            {
                error "duplicate \".\$$dirv\" (used at $dirvorg{$dirv})" if defined($langswitchpos);
                
                $langswitch_required = 1;
                $langswitchpos       = length($page_content);
                $dirvorg{$dirv}      = "$thisfile:$thisline";
            }
            elsif ($dirv eq "hack_langsvisible")
            {
                error "duplicate \".\$$dirv\" (used at $dirvorg{$dirv})" if $#langsvisible >= 0;
                
                error "\".\$$dirv\" without prior \$langs"
                    if $#langs < 0;

                foreach $x (split(/\s*,\s*/, $args))
                {
                    error "invalid language name \"$x\"" if $x =~ /\W/;
                    error "language '$x' requested in \".\$$dirv\" wasn't specified in \".\$langs\""
                        if PosInList($x, @langs) < 0;

                    push @langsvisible, $x;
                }
            }
            elsif ($dirv eq "strip_emptylines")
            {
                if    ($args eq "0"       ||
                       $args =~ /^no$/i   ||
                       $args =~ /^off$/i)
                {
                    $strip_emptylines = 0;
                }
                elsif ($args eq "1"       ||
                       $args =~ /^yes$/i  ||
                       $args =~ /^on$/i)
                {
                    $strip_emptylines = 1;
                }
                else
                {
                    error "invalid strip_emptylines state \"$args\"";
                }
            }
            elsif ($dirv eq "define"  ||  $dirv eq "redefine")
            {
              my $name;
              my $nargs = 0;
              my $value;
                
                $args =~ /^(\w+)\s*\=\s*(.*)$/s &&
                do {
                    $name  = $1;
                    $value = $2;
                    1;
                }
                ||
                $args =~ /^(\w+)\s*\((\d+)\)\s*\=\s*(.*)$/s &&
                do {
                    $name  = $1;
                    $nargs = $2;
                    $value = $3;

                    error "number of arguments for define is out of range [0..9]"
                      if $nargs < 0  ||  $nargs > 9;

                    1;
                }
                ||  error "syntax error in .\$$dirv";

                warning "\"$name\" redefined (previously defined at", $defines_origin{$name}, ")"
                    if exists($defines{$name})  &&  $dirv ne "redefine";

                $defines       {$name} = $value;
                $defines_nargs {$name} = $nargs;
                $defines_origin{$name} = "$thisfile:$thisline";
            }
            elsif ($dirv eq "definexline"  ||  $dirv eq "redefinexline")
            {
              my $name;
              my $nargs = 0;

                $args =~ /^(\w+)\s*$/ &&
                do {
                    $name  = $1;
                    1;
                }
                ||
                $args =~ /^(\w+)\s*\((\d+)\)\s*$/ &&
                do {
                    $name  = $1;
                    $nargs = $2;

                    error "number of arguments for define is out of range [0..9]"
                      if $nargs < 0  ||  $nargs > 9;

                    1;
                }
                ||  error "syntax error in .\$$dirv";

                warning "\"$name\" redefined (previously defined at", $defines_origin{$name}, ")"
                    if exists($defines{$name})  &&  $dirv ne "redefinexline";

                $defines       {$name} = "";
                $defines_nargs {$name} = $nargs;
                $defines_origin{$name} = "$thisfile:$thisline";

                $multiline = 1;
                $multiline_start = $thisline;
                $xlineref = \ ($defines{$name});
            }
            elsif ($dirv eq "undef")
            {
              my $name;

                $args =~ /^(\w+)\s*$/ || error "invalid name for .\$$dirv";
                $name = $1;
                
                delete $defines       {$name};
                delete $defines_nargs {$name};
                delete $defines_origin{$name};
            }

            elsif ($dirv eq "endverbatim")
            {
                error "\".\$$dirv\" without prior \".\$verbatim\"";
            }
            elsif ($dirv eq "endxline")
            {
                error "\".\$$dirv\" without prior \".\$definexline\"";
            }
            else
            {
                error "unknown directive \"\$$dirv\"";
            }

            next READLINE;
        }

        # Include?
        elsif ($line =~ /^\.([\+-])\s*(.*)/)
        {
          my $in_sys_state;
            
            $dirv = $1;
            $args = $2;
            
            error "include file name missing" if $args eq "";
            error "too many nested includes" if ($include_depth >= 20);

            $in_sys_state = $in_sysinclude;
            $in_sysinclude = $in_sysinclude || ($dirv eq "-");
            $include_depth++;
            ReadOneFile($args);
            $include_depth--;
            $in_sysinclude = $in_sys_state;
            
            next READLINE;
        }

        elsif ($line =~ /^\.(\w+)\s*(.*)/)
        {
            $dirv = $1;
            $args = $2;

            if    ($dirv eq "index")
            {
              my $index_label = sprintf("il_%06d", $index_labelctr++);

                $index_content .= ":$outfile#$index_label|$args\n";
                $page_content  .= "<A NAME=\"$index_label\">";
            }
            elsif ($dirv eq "isframeset")
            {
                $page_isframeset = 1;
            }
            elsif ($dirv eq "isssi")
            {
                $page_isssi = 1;
            }
            else
            {
                error "unknown '.'-directive";
            }

            next READLINE;
        }

        # Unknown...
        elsif ($line =~ /^\./)
        {
            error "unknown '.'-directive";
        }

        # ELSE

        #### Phase 7 #################################################
        #  Add the line to content
        ############################################################## 
        $page_content .= $line . "\n";
    }
    # ))))

    error "unclosed .\$verbatim at EOF (started on line $verbatim_start)"     if $verbatim;
    error "unclosed .\$definexline at EOF (started on line $multiline_start)" if $multiline;
    
    close(SRC);

    $thisfile = $upfile;
    $thisline = $upline;
}


sub ReadConfig()
{
  my    $dir;
  my    $file;
  local *CONF;

    $srcfilesusedref = \%configs_used;
    FINDCONFIG: foreach $dir (".", $sitedir, $publdir)
    {
        next FINDCONFIG if !defined($dir)  ||  $dir eq "";

        foreach $file ("srchtml2html.conf", ".srchtml2html")
        {
            if (open(CONF, "<$dir/$file"))
            {
                close(CONF);
                $reading_config = 1;
                ReadOneFile("$dir/$file");
                $reading_config = 0;
                last FINDCONFIG;
            }
        }
    }

    # Restore clean state
    $page_content = "";
    $curlang      = "*";
}


sub ReadSource()
{
    $srcfilesusedref = \%sources_used;
    ReadOneFile($infile);
    
    # Autoset omitted values
    @langsvisible    = @langs               if $#langsvisible < 0;

    $page_charset    = $page_defcharset     if !defined($page_charset);
    $page_title      = "UNTITLED"           if !defined($page_title);
    $page_appearance = $page_defappearance  if !defined($page_appearance);
    $page_appearance = ""                   if !defined($page_appearance);

    # ...and do some hacks if required...
    $imagdir         = $hack_imagdir        if  defined($hack_imagdir);
}


sub WriteFile()
{
    open(DST, ">$outfile") ||
        die "$0: unable to open \"$outfile\" for output -- $!\n";

    unless (defined($page_isssi))
    {
        print DST "<HTML>\n";
        print DST "<HEAD>\n";
        #    print DST "    <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=$page_charset\">", "\n"
        #        if defined($page_charset);
        print DST "    ", META_GENERATOR(),                           "\n";
        print DST "    ", META_XXX("DATE",        localtime()),       "\n";
        print DST "    ", META_XXX("DESCRIPTION", $page_description), "\n"
            if defined($page_description);
        print DST "    ", META_XXX("KEYWORDS",    join(", ", split(/\s*,\s*/, $page_keywords))), "\n"
            if defined($page_keywords);
        print DST "    <TITLE>$page_title</TITLE>\n";
        print DST "</HEAD>\n";
        print DST "<BODY", TagFlags($page_appearance), ">\n" unless defined($page_isframeset);
    }

    if (defined($langswitchpos))
    {
        print DST "<IMG SRC=\"$page_logo\" ALIGN=left>\n"
            if defined($page_logo);
        print DST "<H1 ALIGN=center>", $page_heading, "</H1>\n<BR CLEAR=all>\n\n"
            if defined($page_heading);
        print DST substr($page_content, 0, $langswitchpos);
        print DST LANG_SWITCH();
        print DST substr($page_content, $langswitchpos);
    }
    else
    {
        print DST "<IMG SRC=\"$page_logo\" ALIGN=left>\n"
            if defined($page_logo);
        print DST LANG_SWITCH()
            if $langswitch_required;
        print DST "<H1 ALIGN=center>", $page_heading, "</H1>\n<BR CLEAR=all>\n\n"
            if defined($page_heading);

        print DST $page_content;
    }
    
    unless (defined($page_isssi))
    {
        print DST "</BODY>\n" unless defined($page_isframeset);
        print DST "</HTML>\n";
    }    

    close(DST);
}


sub WriteIndex()
{
    return unless defined($idxfile);
    
    open(IDX, ">$idxfile") ||
        die "$0: unable to open \"$idxfile\" for output -- $!\n";

    print IDX $index_content;
    
    close(IDX);
}


sub WritePpInfo()
{
  my $name;
  my $deps = "";
  my $pos;

    open(DST, ">$outfile") ||
        die "$0: unable to open \"$outfile\" for output -- $!\n";
        
    if ($pp_defines)
    {
        LISTDEFINES: foreach $name (sort(keys(%defines)))
        {
            next LISTDEFINES
                if defined($pp_defslist)  &&  index(",$pp_defslist,", ",$name,") < 0;
                
            if    ($pp_defines_shell)
            {
                print DST "${name}=", quotemeta($defines{$name}), "\n";
            }
            elsif ($pp_defines_perl)
            {
                print DST "my \$${name}=\"", quotemeta($defines{$name}), "\";\n";
            }
            else
            {
                print DST "# $defines_origin{$name}\n";
                if (index($defines{$name}, "\n") >= 0)
                {
                    print DST ".\$definexline $name";
                    print DST "($defines_nargs{$name})" if $defines_nargs{$name} > 0;
                    print DST "\n$defines{$name}" # \n is from define value
                }
                else
                {
                    print DST ".\$define $name";
                    print DST "($defines_nargs{$name})" if $defines_nargs{$name} > 0;
                    print DST "=$defines{$name}\n";
                }
            }
        }
    }
    if ($pp_includes)
    {
        # I. Create a list of dependent files
        # 1. Destination file
        $deps .= $destfile if $destfile ne "-";
        # 2. filename.d
        $deps .= " $outfile" if $outfile ne "-";
        # 3. Trailing ":"
        $deps .= ":";

        # II. Append a list of dependencies
        # 1. Config files if required
        if ($pp_includes_all)
        {
            foreach $name (sort(keys(%configs_used)))
            {
                $deps .= " $name";
            }
        }
        # 2. Source and include files
        foreach $name (sort(keys(%sources_used)))
        {
            $deps .= " $name";
        }

        # III. Write them
        # III. Write into file
        $pos = 0;
        foreach $name (split(/ /, $deps))
        {
            if ($pos + length($name) + 1 > 79)
            {
                print DST "\\\n ";
                $pos = 1;
            }
    
            print DST "$name ";
            $pos += length($name) + 1;
        }

        print DST "\n";
    }

    close(DST);
}


######################################################################
#                                                                    #
#    MAIN PART                                                       #
#                                                                    #
######################################################################

ParseCmdLine();
SetPredefined();
ReadConfig();
ReadSource();
if (!$pp_only)
{
    WriteFile();
    WriteIndex();
}
else
{
    WritePpInfo();
}
