#!/usr/bin/perl -w
# Freetable html tables generator
# Copyright (c) 1999 Tomasz Wgrzanowski <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'.

use Getopt::Std;

init();
while(<>) {
if ( /<wwwtable(\s?.*)>/i ) { table_parse(0) } else { print }
}

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

while(<>) {
if ( /<wwwtable(\s?.*)>/i ) { table_entry_content_large_append(\%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(\%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 init {
my %opts;
getopts('bcw',\%opts);
$config{comment} = $opts{c};
$config{defaultcell} = ($opts{b})?'':'&nbsp;';
$config{min_row} = 1;
$config{min_col} = 1;
$config{tablewarn} = ($opts{w})?'':
"<!-- WARNING: The following table was produced by freetable.               -->
<!--          Unless you are sure you know what you are doing, you should  -->
<!--          not edit it here. Instead, edit the freetable source         -->
<!--          specification for this table. Then use freetable to rebuild  -->
<!--          the HTML for this table.                                     -->

";
}

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 {
my ( $entry, $data ) = @_;
$data =~ /^\s*(.*)$/;
$$entry{cont}[-1] .= (($$entry{cont}[-1] and $1)?' ':'').$1;
}

sub table_entry_content_large_append {
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 ($config{min_row}..$max_row) {
if ( $row =~ /$def_row/ ) {
foreach my $col ($config{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($config{min_row}..$max_row) {
foreach my $col($config{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] = $config{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;
$table_text.= $config{tablewarn};
$table_text.= "<table$table_open_data>\n";
foreach my $row($config{min_row}..$max_row) {
$table_text.= "  <tr>\n";
foreach my $col($config{min_col}..$max_col) {
$table_text.= "    <!-- cell ($row,$col) -->\n" if ( $config{comment} );
$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";
print $table_text unless ( $level );
$table_text;
}

=head1 NAME

freetable - tool for making html tables generation easier

=head1 SYNOPSIS

B<freetable> [-bcw] F<filename>

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

F<-b>     Do not insert F<&nbsp;> to empty cells to make lowered 3D apperance

F<-w>     Do not print a warning before each generated table that
you shouldnt change it. You should change its source.

=head1 DESCRIPTION

This is free replacement of 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
searchs for line starting table:
<wwwtable [options]>
Then it analizes 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 table tags. There is no magic inside

preamble is any html text. It will be put in front of table.

cell is either normal_cell(td tag) or header_cell(th tag)

  normal_cell:
  (row,col) options
  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 is anything. It may contain tags, even nested wwwtables !

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

You can also use :

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

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

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

If many re matches one cell both their options and content are
concatetated.

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 wwwtable and want to change your tool, you
should read this. Most of this is about regexps handling.

Table header fields can be specified by regexps ex : ((1,*)). It was
impossible in 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 oyu 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

=cut
