package Tk::HTML::Handler; require HTML::Parse; require Tk::HTML::Form; use strict; use Carp; delete $HTML::Element::OVERLOAD{'""'}; use vars qw($VERSION $AUTOLOAD); $VERSION = '3.002'; # $Id: //depot/Tk-HTML/HTML/Handler.pm#2$ sub HTML::Element::enclosing { my $self = shift; my $must = shift; my $p = $self; while (defined $p) { my $ptag = $p->{'_tag'}; for (@_) { return $p if $ptag eq $_; } $p = $p->{'_parent'}; } Carp::croak $self->tag . " is not in ".join(' ',@_) if ($must); return undef; } my %FontTag = ('CITE' => 'I', 'STRONG' => 'B', 'EM' => 'B', 'TT' => 'KBD', 'SAMP' => 'CODE'); BEGIN { no strict 'refs'; my $tag; foreach $tag (qw(b i samp code kbd strong em var cite dfn tt)) { *{"$tag"} = \&FontTag; } foreach $tag (qw(address html blink)) { *{"$tag"} = \&DoesNothing; } foreach $tag (qw(dl ul menu dir ol)) { *{"$tag"} = \&List; } foreach $tag (1..6) { *{"h$tag"} = \&Heading; } foreach $tag (qw(tagAdd insert)) { *{"$tag"} = sub { shift->{widget}->$tag(@_) }; } } *th = \&td; *link = \&a; sub AUTOLOAD { my $what = $AUTOLOAD; # print "AUTOLOAD:$what\n"; my($package,$method) = ($what =~ /^(.*)::([^:]*)$/); warn "Don't know how to $method"; if (@_ > 2 && ref($_[2])) { print $_[2]->as_HTML,"\n"; } else { print "$what(",join(',',@_),")\n"; } *{"$what"} = sub { return 1 }; goto &$what; } use strict; sub Widget { shift->{widget} } sub DESTROY { } sub new { my ($class,%args) = @_; my $w = $args{widget}; $w->delete('0.0','end'); $args{NL} = 2; $args{BODY} = 1; $args{Count} = 0; $args{'List'} = []; $args{'FORM'} = []; # all forms defined for this document $args{'Text'} = []; # Current place to send Text $args{'Option'} = []; # Current place to send Option return bless \%args,$class; } sub CurrentForm { my ($w,$f) = @_; if (@_ > 1) { $w->{'CurrentForm'} = $f; } return $w->{'CurrentForm'}; } sub GenTag { my $w = shift; my $prefix = shift; my $tag = $prefix . ++$w->{Count}; $w->{'GenTag'} = [] unless (exists $w->{'GenTag'}); push(@{$w->{'GenTag'}},$tag); $w->{widget}->tagConfigure($tag,@_) if (@_); return $tag; } sub TextHandler { my $w = shift; if (@_) { push(@{$w->{'Text'}},Tk::Callback->new(@_)); } else { return (@{$w->{'Text'}}) ? $w->{'Text'}[-1] : undef; } } sub nl { my ($w,$n) = @_; while ($w->{'NL'} < $n) { $w->insert('insert',"\n"); $w->{'NL'}++; } } sub ElemTag { my ($w,$elem) = @_; my $tag = uc $elem->tag; $w->tagAdd($tag,$elem->{'_Start_'},$elem->{'_End_'}); } sub FontTag { my ($w,$f,$elem) = @_; if (!$f) { my $tag = uc $elem->tag; $tag = $FontTag{$tag} if (exists $FontTag{$tag}); $w->tagAdd($tag,$elem->{'_Start_'},$elem->{'_End_'}); } return $f; } sub meta { my ($w,$f,$elem) = @_; return 0; } sub font { my ($w,$f,$elem) = @_; # print format_attr($elem),"\n" if ($f); return $f; } sub nobr { my ($w,$f,$elem) = @_; print format_attr($elem),"\n" if ($f); return $f; } sub body { my ($w,$f,$elem) = @_; $w->{'BODY'} = 1; return $w; } sub script { my ($w,$f,$elem) = @_; $w->{'BODY'} = 0; return $w; } sub bgsound { my ($w,$f,$elem) = @_; return 0; } sub head { my ($w,$f,$elem) = @_; $w->{'BODY'} = 0; return $w; } sub a { my ($h,$f,$elem) = @_; if (!$f) { my $w = $h->{widget}; my $href = $elem->attr('href'); my $name = $elem->attr('name'); if ($href) { my $tag = $h->GenTag('HREF',-underline => 1); $w->tagAdd($tag,$elem->{'_Start_'},$elem->{'_End_'}); $w->tagBind($tag,'',[$w,'HREF',$href,'GET']); $w->tagBind($tag,'',[$w,'Callback','-showlink',$href]); } if ($name) { $w->tagAdd($name,$elem->{'_Start_'},$elem->{'_End_'}); push(@{$h->{'GenTag'}},$name); } } return $f; } sub li { my ($w,$f,$elem) = @_; if ($f) { my $list = $elem->enclosing(1,qw(ul ol dir menu)); if ($list->tag eq 'ol') { my $n = ++$list->{Num}; $w->insert('insert',"\n $n. "); } else { # $w->insert('insert',"\n \xA8 ",['symbol']); $w->insert('insert',"\n \xB7 ",['symbol']); } } return $w; } sub dt { my ($w,$f,$elem) = @_; $w->nl(1+$f); return $w; } sub dd { my ($w,$f,$elem) = @_; $w->nl(1); return $w; } sub tr_grid { my ($w,$f,$elem) = @_; my $table = $elem->enclosing(1,'table'); if ($f) { $table->{Col} = 0; } else { # print format_attr($elem),"\n"; $table->{Row}++; } return $w; } sub p { my ($w,$f,$elem) = @_; $w->{'BODY'} = 1; $w->nl(2); return $w; } sub br { my ($w,$f,$elem) = @_; return 0 unless $f; $w->{'BODY'} = 1; if (@{$w->{'Text'}}) { $w->{'Text'}[-1]->Call("\n"); } else { $w->nl(1); } return $w; } sub hr { my ($h,$f,$elem) = @_; return 0 unless $f; my $w = $h->{widget}; my $r = $w->Frame(-height => 2, -width => $w->cget('-width')*140, -borderwidth => 1, -relief => 'sunken', ); $h->nl(1); $w->window('create','insert','-window' => $r, -pady => 0, -padx => 0); $h->{NL} = 0; $h->{'BODY'} = 1; $h->nl(1); return $f; } sub DeEscape { my ($var,$text) = @_; $$var .= HTML::Entities::decode($text); } sub td_grid { my ($h,$f,$elem) = @_; my $table = $elem->enclosing(1,'table'); if ($f) { $elem->{Text} = ""; $h->TextHandler([\&DeEscape,\$elem->{Text}]); } else { my $tw = $table->{widget}; my @elem = (); my $al = $elem->{ALIGN}; if (defined $al) { push(@elem,-justify => 'right',-anchor => 'e') if ($al =~ /RIGHT/i); } my $widget = $elem->{'widget'}; unless (defined $widget) { $widget = $tw->Label(-relief => 'ridge',@elem, -text => $elem->{Text}, config($elem, -background => 'bgcolor')); } $widget->grid(-in => $tw, -row => $table->{Row}, -column => $table->{Col}, config($elem, -rowspan => 'rowspan', -columnspan => 'colspan'), -sticky => 'nsew'); pop(@{$h->{'Text'}}); $table->{Col}++; } return $f; } sub broken_td { my ($h,$f,$elem) = @_; my $row = $elem->enclosing(1,'tr'); my $table = $row->enclosing(1,'table'); my $tw = $table->{widget}; if ($f) { my $w = $h->{'widget'}; $elem->{'widget'} = $w; # my $class = ref($w); # my @args = (); # foreach my $opt ($w->configure) # { # if (@$opt != 2) # { # my $val = $opt->[-1]; # my $def = $opt->[-2]; # push(@args,$opt->[0],$val) if defined($val) && (!defined($def) || $val ne $def); # } # } # print join(' ','New:',@args),"\n"; # print format_attr($elem),"\n"; my @elem = (); my $al = $elem->attr('align'); if (defined $al) { push(@elem,-justify => 'right',-anchor => 'e') if ($al =~ /RIGHT/i); } my $widget = Tk::HTML->new($tw, -relief => 'ridge',@elem, config($elem, -background => 'bgcolor'), -width => 0, -height => 0); $widget->grid(-in => $tw, -row => $table->{Row}, -column => $table->{Col}, config($elem, -rowspan => 'rowspan', -columnspan => 'colspan'), -sticky => 'nsew'); $h->{'widget'} = $widget; } else { my $widget = $h->{'widget'}; # $widget->GeometryRequest(0,0); $h->{'widget'} = $elem->{widget}; $table->{Col}++; } return $f; } sub format_attr { my $elm = shift; my $str = '<'.$elm->tag.' '; my $sep = ''; my @list = %$elm; while (@list) { my ($key,$val) = splice(@list,0,2); next if $key =~ /^_/; $str .= "$sep$key=\"$val\""; $sep = ', '; } return $str . '>'; } sub config { my $elem = shift; my @args; while (@_) { my ($opt,$attr) = splice(@_,0,2); my $val = $elem->attr($attr); push(@args,$opt => $val) if defined $val; } return @args; } sub table { my ($h,$f,$elem) = @_; return $f; } sub td { my ($h,$f,$elem) = @_; return $f; } sub tr { my ($h,$f,$elem) = @_; return $h->br($f,$elem) } sub table_grid { my ($h,$f,$elem) = @_; if ($f) { my $w = $h->Widget; $elem->{widget} = $w->Frame(config($elem,-width => 'width', -height => 'height')); $elem->{Row} = 0; $elem->{Col} = 0; $w->window('create','insert',-window => $elem->{widget}); # print format_attr($elem),"\n"; } else { } return $h; } sub form { my ($w,$f,$form) = @_; $w->{'BODY'} = 1; if ($f) { $form->{OldForm} = $w->CurrentForm; bless $form,'Tk::HTML::Form'; push(@{$w->{'FORM'}},$form); $form->{'Values'} = []; $form->{'Owner'} = $w; $w->CurrentForm($form); } else { my $what; my @val = (); foreach $what (@{$form->{'Values'}}) { my $val = $what->[1]; if (ref($val)) { $val = $val->Call(); } push(@val,$val); } $form->{'Reset'} = \@val; $w->CurrentForm(delete $form->{OldForm}); } $w->nl(1); return $w; } sub input { my($w,$f,$elem) = @_; return 0 unless $f; my $form = $w->CurrentForm; my $type = $elem->attr('type'); $elem->attr(type => ($type = 'TEXT')) unless (defined $type); $type = "\U$type"; $form->$type($elem); return $w; } sub option { my ($w,$f,$elem) = @_; if ($f) { push(@{$w->{'option'}},$elem); } else { pop(@{$w->{'option'}}); } return $f; } sub OptionText { my ($h,$mb,$text) = @_; my $elem = $h->{'option'}[-1]; if (defined $elem) { my $val = $elem->attr('value'); $text =~ s/^\s+//; $text =~ s/\s+$//; $elem->attr('value' => $text) unless ($val); if ($elem->attr('value') ne $text) { $mb->{'FORM_MAP'} = {} unless (exists $mb->{'FORM_MAP'}); $mb->{'FORM_MAP'}{$text} = $elem->attr('value'); } $mb->options([$text]); $mb->setOption($text) if ($elem->attr('selected')); } else { confess "$text outside option"; } } sub MultipleText { my ($h,$lb,$text) = @_; $text =~ s/^\s+//; $text =~ s/\s+$//; my $elem = $h->{'option'}[-1]; if (defined $elem) { my $index = $lb->index('end'); $elem = {} unless (defined $elem); $elem->{'VALUE'} = $text unless (exists $elem->{'VALUE'}); if ($elem->{'VALUE'} ne $text) { $lb->{'FORM_MAP'} = [] unless (exists $lb->{'FORM_MAP'}); $lb->{'FORM_MAP'}[$index] = $elem->{'VALUE'}; } $lb->insert($index,$text); $lb->selection('set',$index) if (defined $elem->{'SELECTED'}); } else { confess "$text outside option"; } } sub select { my($h,$f,$elem) = @_; if ($f) { $h->{NL} = 0; my $w = $h->Widget; my $form = $h->CurrentForm; $h->{'option'} = []; if ($elem->attr('multiple') || (defined $elem->{'size'} && $elem->{'size'} > 1)) { my $size = $elem->attr('size'); $size = 15 unless ($size); my $e = $w->Scrolled('Listbox',-height => $size,-scrollbars => 'e'); $e->configure(-selectmode => 'multiple') if $elem->attr('multiple'); $w->window('create','insert',-window => $e); if (defined $form) { my $var = $form->Variable($elem); $$var = Tk::Callback->new([\&Tk::HTML::Form::MultipleValue,$e]); } $h->TextHandler([\&MultipleText,$h,$e]); } else { my $buttonvar = "__not__"; my $mb = $w->Optionmenu(-textvariable => \$buttonvar,-relief => 'raised'); $w->window('create','insert',-window => $mb); if (defined $form) { my $var = $form->Variable($elem); $$var = Tk::Callback->new([\&Tk::HTML::Form::OptionValue,$mb,\$buttonvar]); } $h->TextHandler([\&OptionText,$h,$mb]); } } else { pop(@{$h->{'Text'}}); delete $h->{'option'}; } return $f; } sub textarea { my($h,$f,$elem) = @_; if ($f) { my $rows = $elem->attr('rows') || 20; my $cols = $elem->attr('cols') || 12; my $form = $h->CurrentForm; my $w = $h->Widget; $elem->{'NAME'} = '__inconnu__' if ! defined $elem->{'NAME'}; my $t = $w->Scrolled('Text',-wrap => 'none', -relief => 'sunken', -scrollbars => 'se', -width => $cols, -height => $rows); $w->{'textarea'} = $t; if (defined $form) { my $var = $form->Variable($elem); $$var = Tk::Callback->new([$t,'Contents']); } $w->window('create','insert',-window => $t); $h->{NL} = 0; $h->TextHandler([$t,'insert','end']); } else { pop(@{$h->{'Text'}}); } return $f; } sub base { print STDERR "base(",join(',',@_),")\n"; my($h,$f,$elem) = @_; $h->{'BODY'} = 0; print STDERR "base elem=$elem\n"; my $w = $h->Widget; $w->configure(-base => $elem->attr('href')); return 1 } sub isindex { my($h,$f,$elem) = @_; $h->{'BODY'} = 0; if ($f) { my $w = $h->{widget}; $h->hr($f,$elem); $w->insert('end','This is a searchable index, enter keyword(s) : '); my $e = $w->Entry; $e->bind('',[$w,'call_ISINDEX',$e]); $w->window('create','end',-window => $e); $h->{NL} = 0; $h->hr($f,$elem); } return $f; } sub img { my ($h,$f,$elem) = @_; return 0 unless $f; my $w = $h->{widget}; my $alt = $elem->attr('alt') || ">>Missing IMG<<"; my $al = $elem->attr('align'); my @al = (-align => 'baseline'); if (defined $al) { my $al = "\U$al"; if ($al eq "MIDDLE") { @al = (-align => 'center') } elsif ($al eq "BOTTOM") { @al = (-align => 'baseline') } elsif ($al eq "TOP") { @al = (-align => 'top') } else { print "Align '$al'?\n"; } } my $l = $w->Label(-text => $alt); my $td = $elem->enclosing(0,qw(td th)); if ($td && 0) { $td->{'widget'} = $l; } else { $w->window('create','insert','-window' => $l, @al); $h->{NL} = 0; } my $src = $elem->attr('src'); $w->FindImage($src,$l) if ($src); my $a = $elem->enclosing(0,'a'); if ($a || $elem->attr('image')) { $l->configure('-cursor' => "top_left_arrow", -borderwidth => 3, -relief => 'raised'); if ($elem->attr('ismap') && $a) { $l->bind('<1>',[$w,'IMG_CLICK',$l,'ISMAP',$a->attr('href')]); } elsif ($elem->attr('image')) { $l->bind('<1>',[$w,'IMG_CLICK',$l,'IMAGE',$f,$elem->attr('name')]); } elsif ($a) { $l->bind('<1>',[$w,'IMG_CLICK',$l,'AREF',$a->attr('href')]); } } return $f; } sub title { my ($w,$f,$elem) = @_; if ($f) { $w->{TITLE} = ""; $w->TextHandler(sub { $w->{TITLE} .= shift }); $w->{'BODY'} = 0; } else { $w->{widget}->toplevel->title($w->{TITLE}); pop(@{$w->{'Text'}}); } return $w; } sub Heading { my ($w,$f,$elem) = @_; $w->nl(2); if (!$f) { my $tag = uc $elem->tag; my $align = $elem->attr('align'); $w->{widget}->tagConfigure($tag,-justify => lc($align)) if ($align); $w->ElemTag($elem); } return $w; } sub blockquote { my ($w,$f,$elem) = @_; if ($f) { $w->nl(1); } else { $w->ElemTag($elem); $w->nl(1); } return $w; } sub center { my ($w,$f,$elem) = @_; if ($f) { $w->nl(1); } else { $w->ElemTag($elem); $w->nl(1); } return $w; } sub DoesNothing { my ($w,$f,$elem) = @_; return $f; } sub pre { my ($h,$f,$elem) = @_; $h->{'PRE'} = $f; if (!$f) { $h->tagAdd('CODE',$elem->{'_Start_'},$elem->{'_End_'}); } return $f; } sub List { my ($w,$f,$elem) = @_; if ($f) { $elem->{Num} = 0; push(@{$w->{'List'}},['LI' . $elem->tag,0,$elem->{'_Start_'}]); my $depth = @{$w->{'List'}}; if ($depth > 1) { my $len = ($depth - 1) * 20; my $tag = $w->GenTag($elem->tag . "temp", -lmargin1 => $len, -lmargin2 => $len, -rmargin => $len); $w->tagAdd($tag,${${$w->{'List'}}[$depth-2]}[2],${${$w->{'List'}}[$depth-1]}[2]); } } else { my $depth = @{$w->{'List'}}; if ($depth > 1) { ${${$w->{'List'}}[$depth - 2]}[2] = $elem->{'_End_'}; my $len = $depth * 20; my $tag = $w->GenTag($elem->tag, -lmargin1 => $len, -lmargin2 => $len, -rmargin => $len); $w->tagAdd($tag,${${$w->{'List'}}[$depth - 1]}[2],$elem->{'_End_'}); } pop(@{$w->{'List'}}); } return $f; } sub traverse { my ($h,$elem,$start,$depth) = @_; my $e = ($start) ? '' : '/'; # print ' 'x$depth," ",(ref $elem) ? "<$e".$elem->tag.'>' : $elem,"\n"; if (ref $elem) { my $tag = $elem->tag; my $posn = $h->{widget}->index('insert'); if ($start) { $elem->{'_Start_'} = $posn; } else { $elem->{'_End_'} = $posn; } return $h->$tag($start,$elem); } else { my $text = $elem; if (defined(substr($text,0,1))) { if (@{$h->{'Text'}}) { $h->{'Text'}[-1]->Call($text); } else { return 0 unless ($h->{'BODY'}); unless ($h->{'PRE'}) { $text =~ s/\n/ /mg; $text =~ s/^\s+//g; $text =~ s/\s\s+/ /g; $text =~ s/\s+$//g; } $text = HTML::Entities::decode($text); if (length(substr($text,0,1))) { my $w = $h->{'widget'}; $w->insert('insert',' ',qw(text)) unless ($h->{NL}); $w->insert('insert',$text,qw(text)); $h->{NL} = 0; $h->{NL} = 1 if ($text =~ /\n$/); } } } return 1; } }