#!/usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
    if $running_under_some_shell;

# Freetable html tables generator
# Copyright (c) 1999, 2000 Tomasz Węgrzanowski <maniek@beer.com>
#
# Freetable is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Thanks to Denis Barbier <barbier@imacs.polytechnique.fr> for contribution
#
# On Debian GNU/Linux systems, the complete text of the GNU General
# Public License can be found in `/usr/share/common-licenses/GPL'.

$version = '0.11';
lang_init ();
init ();
while (<>)  { if ( /<wwwtable(\s?.*)>/i ) { table_parse(0) } else { print } }

sub table_parse {
my ( $level ) = shift;
my ( $table_open_data, $table_close_data, $table_started ) = ( $1, '', 0 );
my ( %cell,%entry );

while (<>) {
if ( /<wwwtable(\s?.*)>/i ) { table_entry_content_append_block (\%entry,table_parse($level+1)); next }
if ( /<\/wwwtable(.*)>/i ) { $table_close_data = $1; last }
if ( /^\s*\(\(\s*(.*)\s*,\s*(.*)\s*\)\)(.*)$/ ) { table_entry_new (\%entry,$1,$2,$3,'h'); $table_started = 1; next }
if ( /^\s*\(\s*(.*)\s*,\s*(.*)\s*\)(.*)$/ )     { table_entry_new (\%entry,$1,$2,$3,'d'); $table_started = 1; next }
if ( $table_started ) { table_entry_content_append_line (\%entry,$_) } else { print }
}

seq_entries (\%entry);
my ($max_row,$max_col) = table_count_max (\%entry);
entries2table  (\%entry,\%cell,$max_row,$max_col);
complete_table (\%cell,$max_row,$max_col);
table_render   ($level,\%cell,$max_row,$max_col,$table_open_data,$table_close_data);
}

sub lang_init {
$warning =
'<!-- WARNING: The following table was produced by freetable.               -->
<!--          Unless know what you are doing, you should not edit it here,  -->
<!--          but edit sources and then run freetable to rebuild this table -->

';

$help = 
'Usage: freetable [options] filename

Options:
  -h, --help             Print this message
  -V, --version          Just print version information and exit
  -c, --comment          Do comment before every cell to point its location
  -b, --no-nbsp          Do not insert &nbsp; to empty cells to make lowered
                         3D apperance
  -w, --warning          Print a warning before each generated table that you
                         should not change generated file, but source.
  -l, --location         Location tags substitution
  -m, --macro [program]  Use macro processor for cells content (default: m4)
';
}

sub init {
use Getopt::Long;
$Getopt::Long::bundling=1;
my (@opts) = ("b|no-nbsp","c|comment","w|no-warning","h|help","V|version","m|macro:s","l|location");
$opt_h=$opt_V=$opt_b=$opt_c=$opt_w=$opt_l=0;
$opt_m='-';
GetOptions(@opts);
if ($opt_m eq "-") { $opt_m = '' }
elsif ($opt_m eq "") { $opt_m = 'm4' }
elsif ($#ARGV == -1 && $ARGV ne '') {
    push(@ARGV,$opt_m);
    $opt_m = '';
}
if ($opt_h) { print $help; exit 0 }
if ($opt_V) { print "Freetable $version\n"; exit 0 }
$defaultcell = ($opt_b)?'':'&nbsp;';
($min_row,$min_col) = (1,1);
$tablewarn   = ($opt_w)?$warning:'';
}

sub table_entry_new {
my ( $entry,$row,$col,$data,$type ) = @_;
push @{$$entry{row }},$row;
push @{$$entry{col }},$col;
push @{$$entry{head}},$data;
push @{$$entry{type}},$type;
push @{$$entry{cont}},'';
}

sub table_entry_content_append_line {
my ( $entry, $data ) = @_;
$data =~ /^\s*(.*)$/;
$$entry{cont}[-1] .= (($$entry{cont}[-1] and $1)?"\n":'').$1;
}

sub table_entry_content_append_block {
my ( $entry, $data ) = @_;
$$entry{cont}[-1] .= "\n".$data;
}

sub seq_entries {
my ($entry) = @_;
my ($prerow,$precol) = (1,1);
return if ( $#{$$entry{row}} < 0 );
foreach my $entrynr( 0..$#{$$entry{row}} ) {
seq_one( $prerow, \$$entry{row}[$entrynr] );
$prerow = $$entry{row}[$entrynr];
$prerow = 1 unless ($prerow =~ /^\d+$/);
seq_one( $precol, \$$entry{col}[$entrynr] );
$precol = $$entry{col}[$entrynr];
$precol = 1 unless ($precol =~ /^\d+$/);
}
}

sub seq_one {
my ( $pre,$act ) = @_;
   if ( $$act eq '=' or $$act eq '' ) { $$act = $pre }
elsif ( $$act eq '*' ) { $$act = '.*' }
elsif ( $$act =~ /^([\+-])(\d*)$/ ) { $$act = $pre + ((($1 eq '+')?1:-1) * (($2 eq '')?1:$2)) }
}

sub entries2table {
my ($entry,$cell,$max_row,$max_col) = @_;
foreach my $entrynr (0..$#{$$entry{row}}) {
my $def_row = $$entry{row}[$entrynr];
my $def_col = $$entry{col}[$entrynr];
foreach my $row ($min_row..$max_row) {
if ( $row =~ /^$def_row$/ ) {
foreach my $col ($min_col..$max_col) { complete_cell ($entry,$cell,$row,$col,$entrynr,$def_row) if ( $col =~ /^$def_col$/ ) }
}
}
}
}

sub complete_cell {
my ( $entry,$cell,$row,$col,$entrynr,$re ) = @_;
$$cell{header} [$row][$col] .= $$entry{head}[$entrynr];
$$cell{content}[$row][$col] .= (($$cell{content}[$row][$col] and $$entry{cont}[$entrynr])?' ':'').$$entry{cont}[$entrynr];
$$cell{type}   [$row][$col]  = $$entry{type}[$entrynr];
if ( $$entry{head}[$entrynr] =~ /(col|row)span\s*=\s*(\S+)/i ) {
my ( $direction, $pan ) = ( $1,$2 );
if ( $direction eq 'row' ) {
foreach my $void_row(($row+1)..($row+$pan-1)) { $$cell{void}[$void_row][$col] = 1 }
} else {
foreach my $void_col(($col+1)..($col+$pan-1)) { $$cell{void}[$row][$void_col] = 1 }
}
}
}

sub table_count_max {
my ( $entry,$max_row,$max_col ) = ( $_[0],0,0 );
foreach my $entrynr(0..$#{$$entry{row}}) {
my $row = $$entry{row}[$entrynr];
my $col = $$entry{col}[$entrynr];
if( $row =~ /^\d+$/ and $row > $max_row ) { $max_row =  $row }
if( $col =~ /^\d+$/ and $col > $max_col ) { $max_col =  $col }
}
( $max_row,$max_col );
}

sub complete_table {
my ( $cell,$max_row,$max_col ) = @_;
foreach my $row ($min_row..$max_row) {
foreach my $col ($min_col..$max_col) {
$$cell{type}   [$row][$col] = 'd'          unless ($$cell{type}   [$row][$col]);
$$cell{header} [$row][$col] = ''           unless ($$cell{header} [$row][$col]);
$$cell{content}[$row][$col] = $defaultcell unless ($$cell{content}[$row][$col]);
}
}
}

sub table_render {
my ( $level,$cell,$max_row,$max_col,$table_open_data,$table_close_data ) = @_;
my ( $table_text,$processed_text );
$table_text .= $tablewarn;
$table_text .= "<table$table_open_data>\n";
foreach my $row ($min_row..$max_row) {
$table_text .= "  <tr>\n";
foreach my $col ($min_col..$max_col) {
$table_text .= "    <!-- cell ($row,$col) -->\n" if ($opt_c);
location_tags_substitute (\$$cell{content}[$row][$col],$row,$col) if ($opt_l);
$table_text .= "    <t$$cell{type}[$row][$col]$$cell{header}[$row][$col]>$$cell{content}[$row][$col]</t$$cell{type}[$row][$col]>\n" unless ($$cell{void}[$row][$col])
}
$table_text .= "  </tr>\n\n"
}
$table_text.= "</table$table_close_data>\n";
if ( $opt_m ) {
if ( $level ) {
use IPC::Open2;
pipe MACROR,MACROW;
open2 \*MACROR,\*MACROW,$opt_m;
print MACROW $table_text;
close MACROW;
foreach (<MACROR>) { $processed_text.=$_ }
close MACROR;
return $processed_text;
} else {
open MACROW,"|$opt_m";
print MACROW $table_text;
close MACROW;
}
} else {
if ( $level ) { return $table_text }
else { print $table_text }
}
}

sub location_tags_substitute {
my ( $cell,$row,$col ) = @_;
$$cell =~ s/<row>/$row/gi;
$$cell =~ s/<col>/$col/gi;
}

##EOF##

=head1 NAME

freetable - tool for making HTML tables generation easier

=head1 VERSION

This manpage describes version 0.11 of freetable.

It may be not 100% accurate if you use different version.

=head1 SYNOPSIS

B<freetable> F<[options]> F<filename>

Possible options are :

I<-h> or I<--help>    Print usage info and exit

I<-V> or I<--version> Print version information and exit

I<-c> or I<--comment> Do comment before every cell to point its location

I<-b> or I<--no-nbsp> Do not insert C<&nbsp;> to empty cells to make lowered
3D apperance

I<-w> or I<--warning> Print a warning before each generated table
that you should not change it. You should change its source.

I<-l> or I<--location> Substitute <row> and <col> flags inside table with correct
cell's location

I<-m> or I<--macro> I<[program]>  Use macro procesor to proces cells content (default: m4)

=head1 SECURITY WARNING

 DO NOT USE MACRO PROCESSOR OVER UNSURE SOURCE
 M4 MAY BE USED TO COMPROMISE YOUR SECURITY
 FOR MORE INFORMATION ON THIS EXEC :
 (info m4 'UNIX commands' syscmd)

=head1 DESCRIPTION

This is free replacement of F<wwwtable>

HTML is great language, but have one horrible flaw :
tables. I spent many hours looking at HTML source I just written
and trying to guess which cell in source is which in browser.

If this also describes you, then read this manpage and your
pain will stop.

Program read HTML source from either stdin or file. Then it
searches for line starting table:

    <wwwtable [options]>

Then it analyzes table, put correct HTML table in this place and
continue searching for the next table.

=head1 TABLE SYNTAX

It is very easy:

    wwwtable :
    <wwwtable [wwwtable_options]>
    [preamble]
    [cell]
    [cell]
    ...
    </wwwtable>

wwwtable_options will be passed to C<E<lt>TABLEE<gt>> tags. There is
no magic inside preamble. It can be any HTML text. It will be simply
put in front of table.

cell is either normal_cell (C<E<lt>TDE<gt>> tag) or
header_cell (C<E<lt>THE<gt>> tag)

    normal_cell :
    (row,col) cell_options
    cell_content

    header_cell :
    ((row,col)) cell_options  
    cell_content

cell_options will be passed to cell tag. There is magic inside
colspan and rowspan keys are parsed to make correct table.

cell_content can be anything. It may contain text, tags, and
even nested wwwtables.

If you use I<-m> (or I<--macro>) option, it will be passed thru m4(1),
with <row> and <col> set to adress of curent cell

row and col are either numbers locating cells, expressions relative to previous cell
or regular expresions to match few of them. Unlike F<wwwtable>, F<freetable> can use regular
expresions for header cells. Also C<*> can be used, and it mean C<.*> really.

Relative expressions are :

I<=> or empty what mean : the same as previous

I<+> or I<+X> what mean : one and X more than previous

I<-> or I<-X> what mean : one and X less than previous

If many definisions adress the same cell all options and contents are
concatenated in order of apperance.

If you want use only regular expresions you must tell program about the last cell :

    <wwwtable>
    (*,1)
    these are colums 1
    (1,*)
    these are rows 1
    (4,4)
    </wwwtable>

=head1 INCOMPATIBILITIES WITH WWWTABLE

If you was formerly user of F<wwwtable> and want to change your tool, you
should read this. Most of this is about regexps handling.
Notice also that F<wwwtable> couldnt do location tags substitution nor macroprocesing.

Option I<-w> has completely oposite meaning. We dont print warnings by default,
and I<-w> or I<--warning> is used to force warnings.

Table header fields can be specified by regexps ex :

    ((1,*))

It was impossible in F<wwwtable>.

Axis counters are 100% orthogonal. This mean that code :

    (*,1) width=30
    (*,2) width=35
    (*,3) width=40
    (=,=)
    Foo

Foo will appear in 3rd column, and if you wanted it to be in 1th
this should be written :

    (*,1) width=30
    (*,2) width=35
    (*,3) width=40
    (=,1)
    Foo

or

    (*,) width=30
    (*,+) width=35
    (*,+) width=40
    (=,1)
    Foo

=head1 SEE ALSO

    B<m4(1)>

=head1 AUTHOR

Tomasz Węgrzanowski <maniek@beer.com>

=cut
