# ============================================================================ package WebTK::Draw; # ============================================================================ # DIRECTIVES # ============================================================================ use Carp; use strict; # ============================================================================ # POD HEADER # ============================================================================ =pod =head1 NAME WebTK::Draw - Write web pages =head1 SYNOPSIS use WebTK::Draw; my $p = WebTK::Draw->new (); $p->html ($p->body ("Hello")); $p->printpage (); =head1 AUTHOR Daniel LaFavers =head1 DESCRIPTION The WebTK::Draw object provides functions for creating web pages. It includes several functions for setting HTTP headers and writing elements. The headers are kept in a hash, and the text of a page is kept in a buffer. The drawpage method renders both the headers and the body, while the draw method renders only the text. The AUTOLOAD feature is used to capture unknown methods and convert them into open/close elements. Thus $p->anyelem (); will generate an element open tag: . Use $p->_anyelem (); to render the element close tag: . Element attributes are set by passing in a reference to a hash. For example, $page->table ({border=>"1"}); generates: WARNING Be careful that all built-in function calls are spelled properly. If not, they will be turned into tags and written into the buffer. For example, $page->print_table (); # Writes an empty tag $page->print_tables (); # Calls the print_tables method =cut # ============================================================================ # CLASSWIDE VARIABLES # ============================================================================ # Define the alias tables # Form shortcuts $WebTK::Draw::alias_tables{form} = [ ["getform", "form", {method=>"get"}], ["postform", "form", {method=>"post"}], ["multiform", "form", {method=>"post", enctype=>"multipart/form-data"}], ["button", "input", {type=>"button"}], ["checkbox", "input", {type=>"checkbox"}], ["file", "input", {type=>"file"}], ["hidden", "input", {type=>"hidden"}], ["image", "input", {type=>"image"}], ["password", "input", {type=>"password"}], ["radio", "input", {type=>"radio"}], ["reset", "input", {type=>"reset"}], ["submit", "input", {type=>"submit"}], ["textinput", "input", {type=>"text"}], ["selected", "option", {selected=>undef}], ["radioch", "input", {type=>"radio", checked=>undef}], ["checkboxch", "input", {type=>"checkbox", checked=>undef}], ]; # ============================================================================ # CLASS METHODS # ============================================================================ # ---------------------------------------------------------------------------- # Constructor =pod =head1 CONSTRUCTOR =over 4 =item $page = WebTK::Draw->new () Creates a new draw object. By default, the constructor loads internal alias tables. To disable this, pass "noalias" to the constructor. You can then load the tables separately if you want using the alias_group method. A draw object has two primary components: =over 4 =item 1) The header hash contains lines that are written as part of the page header. This is initialized to have one value, Content-Type, with its value set to "text/html". If you do not want this default header, call $page->clear_headers(); The header elements are rendered when the drawpage method is called. =item 2) The page body buffer contains the actual HTML page. All of the page generation methods write to the page buffer when they are called in void context. Void context means that the method is called without any variable to accept its output. For example, $page->text ("Pe", "rl"); writes "Perl" into the page buffer, while my $lang = $page->text ("Pe", "rl"); sets $lang to "Perl" without writing anything into the page buffer. =back =back =cut sub new { my $class = shift; my ($self, $buf, @http, @cookie, %alias, $table); # Create the object $self = {}; bless $self, $class; # Set its initial values $self->{buf} = $buf; $self->{varfcn} = undef; $self->{replacevars} = 1; # Set up the HTTP headers push @http, "Content-Type: text/html; charset=iso-8859-1"; $self->{http} = \@http; # Build the empty cookie hash $self->{cookie} = \@cookie; # Build the empty alias hash $self->{alias} = \%alias; $self->{doctype} = ""; # Skip loading the alias tables if asked to return $self if ( defined $_[0] && $_[0] eq "noalias" ); # Load alias groups for $table ( keys %WebTK::Draw::alias_tables ) { $self->alias_group ($WebTK::Draw::alias_tables{$table}); } return $self; } # ============================================================================ # OBJECT METHODS # ============================================================================ =pod =head1 OBJECT METHODS =cut # ============================================================================ # HEADER METHODS # ============================================================================ =pod =head2 Header methods =cut # ---------------------------------------------------------------------------- # header =pod =over 4 =item $page->header ("name", "value"); This sets an HTTP header. The header will be rendered in the form: "$name: $value\n". Headers are kept in a hash and are generated along with the page by the drawpage method. When the object is created, it sets "Content-Type" to "text/html". You can use this function to set cookies, but the cookie method provides an easier interface for setting expiration dates and other cookie values. If the value is omitted, only the name will be printed. This allows you to specify the exact contents of the header. =back =cut sub header { my $self = shift; my $name = shift; my $value = shift; my $http = $self->{http}; if ( $value ) { push @$http, "$name: $value"; } else { push @$http, $name; } } # ---------------------------------------------------------------------------- =pod =over 4 =item $page->clear_headers (); Clears all header values. This is useful to remove the default Content-Type header that is added in the constructor. =back =cut sub clear_headers { my $self = shift; my @empty_list; $self->{http} = \@empty_list; } # ---------------------------------------------------------------------------- # cookie =pod =over 4 =item $page->cookie ($name, $value) or $page->cookie ($cookie_hash); The cookie method accepts two types of input. If two scalar values are passed, they are taken as the cookie name and value, and no other information is set for the cookie. The second form must be used to specify additional information about the cookie. The second form takes a reference to a hash with the following values: $page->cookie ({name=>$name, value=>$value, path=>$path_name, domain=>$domain_name, expires=>$time, secure=>1}); All items, except name and value, are optional. The I of secure is ignored. If it is present the secure option will be set. This funtion only sets cookie headers. Use the WebTK::Cgi module to read cookies. Some of the following text is borrowed from "Webmaster in a nutshell", page 92. =over 4 =item name and value Both name and value strings will are allowed to be any string. The set_cookie function will escape spaces and other special characters. =item path_name The path attribute supplies a URL range for which the cookie is valid. If path is set to /pub, for example, the cookie will be sent for URLs in /pub as well as lower levels such as /pub/docs and pub/images. A pathname of "/" indicates that the cookie will be used for all URLs at that site from which the cookie originated. No path attribute means that the cookie is valid only for the originating URL. =item domain_name This attribute sepcifies a domain name range for which the cookie will be returned. The domain-name must contain at least two dots (.), e.g., .ora.com. This value would cover both www.ora.com and software.ora.com, and any other server in the ora.com domain. =item expires The expires value is either a time value, as returned by time () and modified by you, or a string containing a modifier that specifies some time in the future from now. If you want to make your own time value, do it like this. my $seconds = 60*60*3; # Number of seconds in three hours $cookie_hash{expires} = time () + $seconds; A modifier string provides a shortcut to multiplying all those seconds together. The modifier is a series of space separated adjustments, where each adjustment consists of: =over 2 =item + or -: (optional) + adds the time and - subtracts time. + is assumed. =item number: How many of the units =item unit: Use these codes: s for second m for minute h for hour d for day M for month y for year =back For example $cookie_hash{expires} = "3h 30m"; # Now plus three and a half hours The function will format the time into a proper GMT cookie format, which is something like this: Wed, 01-Sep-96 00:00:00 GMT. The offset parser is available as a callable function, so the following two lines are equivalent. $cookie_hash{expires} = "1d 3m 15s"; $cookie_hash{expires} = time () + $page->time_offset ("1d 3m 15s"); Invalid time expressions will throw an exception. =item secure The secure attribute tells the client to return the cookie only over a secure connection (via SHTTP and SSL). Leaving out this attribute means that the cookie will always be returned regardless of the connection =back =back =cut sub cookie { my $self = shift; my ($defn, $cookie, $safename, $safevalue); # Look for the two scalar form of method if ( scalar @_ == 2 && ! ref $_[0] && ! ref $_[1] ) { $defn = { name=>$_[0], value=>$_[1] }; } elsif ( scalar @_ == 1 && ref $_[0] eq "HASH" ) { $defn = shift; } else { croak "Expecting two scalars or a hash ref"; } # Make sure we have what we need croak "Cookie must specify name and value" if ( ! exists ($defn->{name}) || ! exists ($defn->{value}) ); # Encode the cookie name and value $safename = $defn->{name}; $safename =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg; $safevalue = $defn->{value}; $safevalue =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg; # Add the name and value $cookie = "$safename=$safevalue"; # Handle converting the expiration time if ( exists ($defn->{expires}) ) { my ($when, $gmt); # This is either a time value or a modifier $when = $defn->{expires}; if ( $when !~ m/^\d+$/ ) { $when = time () + $self->time_offset ($when); } # Compute the actual string my(@mname)=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/; my(@dname) = qw/Sun Mon Tue Wed Thu Fri Sat/; my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($when); $year += 1900; $gmt = sprintf("%s, %02d-%s-%04d %02d:%02d:%02d GMT", $dname[$wday],$mday,$mname[$mon],$year,$hour,$min,$sec); # Add it to the cookie $cookie .= "; expires=$gmt"; } # Handle the other values $cookie .= "; path=$defn->{path}" if ( exists ($defn->{path}) ); $cookie .= "; domain=$defn->{domain}" if ( exists ($defn->{domain}) ); $cookie .= "; secure}" if ( exists ($defn->{secure}) ); # Now push this cookie onto our list push @{$self->{cookie}}, $cookie; } # ------------- Time offset parse function - also callable sub time_offset { my $self = shift; my $offset_defn = shift; my %unit = ('s'=>1, 'm'=>60, 'h'=>3600, 'd'=>86400, 'M'=>2592000, 'y'=>31536000); my (@ofs, $o, $offset); @ofs = split / /, $offset_defn; $offset = 0; for $o ( @ofs ) { croak "Bad time adjustment: $o" if ( ! ($o =~ m/([+-]?\d+)([smhdMy])/) ); # Compute the adjustment $offset += $1 * $unit{$2}; } return $offset; } # ============================================================================ # PAGE GENERATION METHODS # ============================================================================ =pod =head2 Page generation methods =cut # ---------------------------------------------------------------------------- # doctype =pod =over 4 =item $page->doctype ($value); Sets a standard document type. Accetable values are: =over 4 =item "xhtml" Generages a standard XHTML 1.1 dtd header =back =back =cut sub doctype { my $self = shift; my $type = shift; if ( $type eq "xhtml" ) { $self->{doctype} = "\n"; } } # ---------------------------------------------------------------------------- #open =pod =over 4 =item $page->open ("element_name", [attributes], [content]); The open method is the workhorse of element generation. The first parameter must be the element name. If the next parameter is a reference to a hash, this is used to define the attributes for the element. ELEMENT CONTENT There are exactly three types of element content. =over 4 =item 1) Empty. Either the name parameter is the only parameter, or it has a name followed by a reference to a hash containing attribute names and values. In this case, only the element open tag is drawn. You can use the close method to draw the close tag. =item 2) The content consists of a single parameter, which is a reference to a list containing scalar values. In this case, each element in the list is enclosed within an element. =item 3) The content consists of one or more scalars. In this case, each scalar is written, without spaces, into a single element. =back XHTML EMPTY ELEMENTS If the element name ends with "/" or "_", then the element is not allowed to have any content, and it is rendered using the Eelem /E format. ATTRIBUTES To specify an attribute that has no value, use {attribute=>undef}. To indicate that an attribute should not be displayed at all, set its value to "-hide-". This is not useful except for overriding default attributes for an alias. (See method alias below). EXAMPLES $page->open ("p");

$page->open ("p", {class=>"info"});

$page->open ("p", "content");

content

$page->open ("p", {class=>"info"}, "content");

content

$page->open ("p", "one", "two");

onetwo

$page->open ("p", ['one', 'two', 'three']);

one

two

three

$page->open ("p", {style="color:red"}, ['one', 'two', 'three']);

one

two

three

$page->open ("br/");
$page->open ("table", {border=>"1"}, $page->open ("tr", [$page->open ("td", ['one','two','three']), $page->open ("td", ['four','four','six'])]));
onetwo three
four fivesix
=back =cut sub open { my $self = shift; my ($elem, $atref); my ($out, $item, $subelem, $listref, $d); $out = ""; # Get the element name if ( ! defined $_[0] ) { croak "Invalid parameter list: no element name"; } # Shift off the element name $elem = shift; # Look for attributes if ( ref $_[0] eq "HASH" ) { # Shift off the attribute hash $atref = shift; } # Is there content? if ( @_ == 0 ) { $out = $self->_elemopen ($elem, $atref); } else { # If the element name ends with "/" or "_" # it is not allowed to have any content. croak "Parameter list not allowed for empty element" if ( $elem =~ m|[/_]$| ); # Validate the content list if ( @_ == 1 && ref $_[0] eq "ARRAY" ) { $listref = $_[0]; $d = 1; # Distribute element definition for each item } else { $listref = \@_; $d = 0; } # Every item in the list needs to be a scalar for $item ( @$listref ) { croak "Invalid parameter list: Non scalar value" if ref $item; } # Draw the element(s) if ( ! $d ) { $out .= $self->_elemopen ($elem, $atref); } for $item ( @$listref ) { if ( $d ) { $out .= $self->_elemopen ($elem, $atref); } $out .= $item; if ( $d ) { $out .= $self->_elemclose ($elem); } } if ( ! $d ) { $out .= $self->_elemclose ($elem); } } # Return or save the value if ( defined wantarray () ) { return $out; } else { $self->{buf} .= $out; } } # ---------------------------------------------------------------------------- # close =pod =over 4 =item $page->close ("element_name"); The close method takes a single parameter and generates a close tag. In void context, the close tag is written into the page buffer. Otherwise, it is returned. =back =cut sub close { my $self = shift; my $elem = shift; if ( defined wantarray () ) { return $self->_elemclose ($elem); } else { $self->{buf} .= $self->_elemclose ($elem); } } # --------------------------------------------------------------------------- # AUTOLOAD =pod =over 4 =item $page->any_element (); Most of the time you will use this shortcut to render elements and their contents. When the Draw module sees any method that is not otherwise defined, it reads the method name and passes it to either the open or close method. If the function name begins with "_", the element name is passed to the close method. Otherwise it is passed to the open method. When the element name is followed by "_" the element is rendered as an XHTML empty element. You can think of the "_" as a substitute for the "/" character. You can also use this shortcut to access alias definitions. EXAMPLES $page->table (); $page->_table ();
$page->br_ ();
$page->alias ("tb", "table"); $page->tb (); =back =cut sub AUTOLOAD { my $self = shift; my $elem = $WebTK::Draw::AUTOLOAD; my $string; # Remove the package name $elem =~ s/.*:://; # Skip some functions we don't want to handle return if ( $elem eq "DESTROY"); # What type of element name do we have? if ( $elem =~ m/^_/ ) { # Render a close $elem =~ s/^_//; $string = $self->_elemclose ($elem); } else { # Render an open $string = $self->open ($elem, @_); } if ( defined wantarray () ) { return $string; } else { $self->{buf} .= $string; } } # ---------------------------------------------------------------------------- # text =pod =over 4 =item $page->text ("text"); This function cleans each parameter by converting characters that could be interpreted as part of the HTML code. In void context the parameters are written into the page data buffer. Otherwise, it returns the concatenated list. Use rawtext or markkup to draw unmodified text. EXAMPLES $page->text (""); <boo> $page->rawtext (""); This function also performs variable replacement, which takes place after conversion of special characters. This means that the value of a variable may contain html text that is not converted. Variables have the form !(name) or !(name,arg,arg,...) Note that variables can not be nested. A variable opened by !( is closed by the first ). Variables are replaced using a function that you provide. The variable is recognized and the name is split on commas. This array is passed to your function, and the return value of your function will be written into the text buffer, replacing the variable name. The set_var_function is used to establish your variable handling function. A full example is provided in the description of that method. This function also performs some simple character formatting using character format blocks. A character format block is enclosed within curly braces. Each format block consists of a format code and text separated by a colon. For example, the following character format block will specify bold text. Here is an example of {b:bold text}. You can nest format blocks within the data part of another block. This text is both {red:{b:red and italic}} A format code block must all be specified on a single line. Formatting Codes b - bold i - italic u - underline red - make text red. You may use the following colors: red, green, blue, yellow, pink, purple, orange #000000 - any color code link - make hypertext link - href|text: {link:http://www.google.com|Go to Google} html - insert any raw html =back =cut sub text { my $self = shift; my ($t, $text, $cleantext); $cleantext = ""; for $text ( @_ ) { $t = $text; $t =~ s/&/&/g; $t =~ s//>/g; $t =~ s/\"/"/g; # What others do I need? $cleantext .= $t; } # Do variable replacement if ( $self->{replacevars} ) { $cleantext =~ s/!\((.+?)\)/$self->_var_lookup($1)/eg; } # Do character formatting my $pattern = '{((\w|\d)+):([^{]+?)}'; while ($cleantext =~ s/$pattern/$self->_char_markup($1,$3)/e) { } if ( defined wantarray () ) { return $cleantext; } else { $self->{buf} .= $cleantext; } } # ---------------------------------------------------------------------------- # text =pod =over 4 =item $page->nl (); Shortcut for $page->text ("\n"); =back =cut sub nl { my $self = shift; my $text = "\n"; if ( defined wantarray () ) { return $text; } else { $self->{buf} .= $text; } } # ---------------------------------------------------------------------------- sub _char_markup { my $self = shift; my $cmd = shift; my $data = shift; my ($color, $font); # Simple markup if ( $cmd eq "b" ) { return "$data"; } if ( $cmd eq "i" ) { return "$data"; } if ( $cmd eq "u" ) { return "$data"; } # Color names for $color ("red", "green", "blue", "yellow", "pink", "purple", "orange", "black", "white") { if ( $cmd eq $color ) { return "$data"; } } # Any color if ( $cmd =~ m/[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]/ ) { return "$data"; } # Link if ( $cmd eq "link" ) { my ($href,$text) = $data =~ m/([^|]+)\|(.+)/; return "$text"; } # Link to external site if ( $cmd eq "linkout" ) { my ($href,$text) = $data =~ m/([^|]+)\|(.+)/; return "$text"; } # Href - insert any html text if ( $cmd eq "html" ) { # are converted to <tags> before we get to this function. # So what we do here is undo those changes $data =~ s/<//g; $data =~ s/"/\"/g; $data =~ s/&/&/g; return $data; } # Unknown command return $data; } # ---------------------------------------------------------------------------- # html =pod =over 4 =item $page->markup (); This method concatenates all parameters. In void context the parameters are written into the page data buffer. Otherwise, it returns the concatenated list. This function is used to add HTML text to the buffer. However, unlike the rawtext function, this function performs variable interpolation. =back =cut sub markup { my $self = shift; my ($t, $text); for $t ( @_ ) { $text .= $t; } # Do variable replacement $text =~ s/!\((.+?)\)/$self->_var_lookup($1)/eg; if ( defined wantarray () ) { return $text; } else { $self->{buf} .= $text; } } # ---------------------------------------------------------------------------- # rawtext =pod =over 4 =item $page->rawtext (); This method concatenates all parameters. In void context the parameters are written into the page data buffer. Otherwise, it returns the concatenated list. No changes is made to the parameters. This is a fallback function that you can use to generate your own HTML code, if necessary. $page->rawtext (""); Note that this method DOES NOT perform variable interpolation. =back =cut sub rawtext { my $self = shift; if ( defined wantarray () ) { return join "", @_; } else { $self->{buf} .= join "", @_; } } # ---------------------------------------------------------------------------- # alias =pod =over 4 =item $page->alias ("alias_name", "element_name", $attr_ref); The alias method defines a name which can be used in place of an element name. The open and close methods first check to see if a given name is an alias. If so, the replacement element is used, and the attributes are initialized to those defined for the alias. Attributes passed into the open function will extend or override the default attributes. To remove a default attribute, override it with a value of "-hide-". EXAMPLES $page->alias ("green", "span", {style=>"color:green"}); $page->green ("This text is green"); This text is green You can use alias to force proper XML format for empty elements. $page->alias ("br", "br/"); $page->br ();
Your attributes extend and override the default attributes $page->alias ("table", "table", {border=>"1"}); $page->table (); # By default, every table has a border
# You can override the border value $page->table ({border=>"0", cellpadding=>"5"});
# To remove the border attribute altogether $page->table ({border=>"-hide-"});
It is also helpful to use an alias to make a shortcut to an element name that is not a valid method name, and therefore can not be processed by the AUTOLOAD feature. For example, if you wanted to make an XML schema, you could have alias definitions like this: $page->alias ("xselem", "xs:element"); $page->xselem ("content"); content =back =cut sub alias { my $self = shift; my $name = shift; my $elem = shift; my $attr = shift; my $alias = $self->{alias}; # Croak if name and element are not defined croak "Invalid alias definition - alias name and element required" if ( ! defined $name || ! defined $elem ); # Create the alias definition hash my %def; # Set the element and default attributes $def{elem} = $elem; if ( defined $attr ) { $def{attr} = $attr; } # Add it to the table $alias->{$name} = \%def; } # ---------------------------------------------------------------------------- #unalias =pod =over 4 =item $page->unalias ("alias_name"); Removes the alias definition. To remove all alias definitions, call this with no parameters. =back =cut sub unalias { my $self = shift; my $name = shift; my $alias = $self->{alias}; if ( defined $name ) { # Delete the named item delete $alias->{$name} if ( exists ($alias->{$name}) ); } else { # Clear the entire alias set my %empty; $self->{alias} = \%empty; } } # ---------------------------------------------------------------------------- # alias_group =pod =over 4 =item $page->alias_group ($listref); Loads a set of alias definitions all at once. The parameter must be an array reference, where each item is another reference to an array that contains the parameters to the alias function. Usually this is accomplished using anonymous array constructors. For example, $myaliaslist = [ ["red", "span", {style=>"color:red"}], ["tdtop", "td", {valign=>"top"}], ["script", "script", {language=>"JavaScript"}] ]; $page->alias_group ($myaliaslist); The WebTK::Draw module defines several such alias tables in a hash variable named %WebTK::Draw::alias_tables. You can display the names and contents of these tables by calling the show_tables function, defined below. Here is how you load one of the pre-defined alias tables yourself. $page->alias_group ($WebTK::Draw::alias_table{form} =back =cut sub alias_group { my $self = shift; my $table = shift; my $defn; croak "Invalid parameter - expecting array reference" if ( ref $table ne "ARRAY"); for $defn ( @$table ) { $self->alias (@$defn); } } # ---------------------------------------------------------------------------- # print_alias =pod =over 4 =item $page->print_alias (); Print a table of all aliases that are currently set. =back =cut sub print_alias { my $self = shift; my $alias = $self->{alias}; my ($a, $def); print "\n"; print "==================================================\n"; print "CURRENT ALIAS DEFINITIONS\n"; print "--------------------------------------------------\n"; for $a ( sort keys %$alias ) { $def = $alias->{$a}; $self->_print_alias_defn ([$a, $def->{elem}, $def->{attr}]); } } # ---------------------------------------------------------------------------- # print_tables =pod =over 4 =item $page->print_tables () The purpose of this function is to document the content of the built-in alias tables. This will dump all of the tables that are loaded by the constructor by default. If you pass "noalias" to the constructor, you can then load the tables individually, if you want, by calling the alias_group method. Note that this does not show the currently loaded alias list. This shows the built-in alias tables that are available within the WebTK::Draw module, and which are loaded by default. Use the print_alias function to print out the list of currently loaded aliases. =back =cut sub print_tables { my $self = shift; my ($tname, $table, $defn); for $tname ( sort keys %WebTK::Draw::alias_tables ) { print "\n"; print "==================================================\n"; print "ALIAS TABLE: \$WebTK::Draw::alias_table{$tname}\n"; print "--------------------------------------------------\n"; $table = $WebTK::Draw::alias_tables {$tname}; for $defn ( @$table ) { $self->_print_alias_defn ($defn); } } } # ---------------------------------------------------------------------------- # comment =pod =over 4 =item $page->comment ("comment text"); Generates a comment element. If called in void context, this writes the comment into the object buffer. Otherwise it returns the comment. =back =cut sub comment { my $self = shift; my $text = shift; my $comment; $comment = ""; if ( defined wantarray() ) { return $comment; } else { $self->{buf} .= $comment; } } # ============================================================================ # OUTPUT METHODS # ============================================================================ =pod =head2 Output methods =cut # ---------------------------------------------------------------------------- # draw =pod =over 4 =item $page->draw (); Returns the text-only portion of the object. Use drawpage (below) to return a string that contains the HTTP headers plus the page buffer. =back =cut sub draw { my $self = shift; return $self->{buf}; } # ---------------------------------------------------------------------------- # drawpage =pod =over 4 =item $page->drawpage (); Returns a string that contains the HTTP headers plus the page. This is the normal method used to display an HTML page. Use the draw method if you are using a draw object to render a portion of a page. =back =cut sub drawpage { my $self = shift; my ($headers, $http, $h, $cookie, $c, $doctype); # Set the headers $headers = ""; # Write the cookies for $c ( @{$self->{cookie}} ) { $headers .= "Set-Cookie: $c\n"; } # Draw the other headers $http = $self->{http}; for $h ( @$http ) { $headers .= "$h\n"; } # Write the blank line to separate headers from the body $headers .= "\n" if ( $headers ne "" ); # Set the doctype return $headers . $self->{doctype} . $self->{buf}; } # ---------------------------------------------------------------------------- # print =pod =over 4 =item $page->print (); Prints the result of $page->draw () to stdout. =back =cut sub print { my $self = shift; print $self->draw (); } # ---------------------------------------------------------------------------- # printpage =pod =over 4 =item $page->printpage () Prints the result of $page->drawpage () to stdout. =back =cut sub printpage () { my $self = shift; print $self->drawpage (); } # ============================================================================ # OTHER FUNCTIONS # ============================================================================ =pod =head2 Other Functions =cut # ---------------------------------------------------------------------------- # set_var_function =pod =over 4 =item $page->set_var_function (\&your_function); This method is used to establish a variable replacement function that you write. When variables appear in the string passed to the text method, variables will be identified and the variable name and arguments will be passed to your function. The return value of your function is replaced in the text. Here is a complete program that demonstrates this. Of course your function can do anything you want it to. #! /usr/bin/perl use WebTK::Draw; my $draw = WebTK::Draw->new (); $draw->set_var_function (\&var); $draw->text ("the time is !(time)\n"); $draw->text ("HELLO !(uc,world)\n"); $draw->text("The sum is !(eval,3*5)\n"); $draw->print (); sub var { my ($name, @args) = @_; if ( $name eq "time" ) { return scalar localtime; } if ( $name eq "uc" ) { return uc $args[0]; } if ( $name eq "eval" ) { return eval $args[0] } return "@_"; } The output of this program will look something like this: the time is Fri Jul 1 23:40:19 2005 HELLO WORLD The sum is 15 =back =cut sub set_var_function { my $self = shift; $self->{varfcn} = shift; } # ---------------------------------------------------------------------------- =pod =over 4 =item $page->ignore_variables (); =item $page->recognize_variables (); This function turns off variable replacement for this draw object. This overrides the function set by set_var_function. No variable replacement will be done. You can call recognize_variables to turn variable handling back on. =back =cut sub ignore_variables { my $self = shift; $self->{replacevars} = 0; } sub recognize_variables { my $self = shift; $self->{replacevars} = 1; } # ============================================================================ # INTERNAL FUNCTIONS # ============================================================================ # ============================================================================ # Generate the element's open tag with its attributes sub _elemopen { my $self = shift; my $elem = shift; my $atref = shift; my ($emptyelem, $alias, $aliasdef, %newattr, $k); # Look up this name in the alias list $alias = $self->{alias}; if ( exists $alias->{$elem} ) { # Get the alias definition $aliasdef = $alias->{$elem}; # Reset the element name $elem = $aliasdef->{elem}; # Set up the attribute list if ( ref $aliasdef->{attr} eq "HASH" ) { # Copy the default attributes to the local hash %newattr = %{$aliasdef->{attr}}; # Add and override using the passed attributes if ( ref $atref eq "HASH" ) { for $k ( keys %$atref ) { $newattr{$k} = $atref->{$k}; } } # Now use the local hash $atref = \%newattr; } } # Look for an indication to render format. # An element name that ends with / or _ sets this. $emptyelem = 0; if ( $elem =~ m|[/_]$| ) { # Remove the mark $elem =~ s|[/_]$||; $emptyelem = 1; } my $open = "<" . $elem; if ( ref $atref eq "HASH" ) { my $at; for $at ( keys %$atref ) { if ( ! defined $atref->{$at} ) { # Draw the name only. $open .= " $at"; } elsif ( $atref->{$at} ne "-hide-" ) { # Draw both name and value $open .= " $at=\"" . $self->text ($atref->{$at}) . "\""; } } } if ( $emptyelem ) { $open .= " />"; } else { $open .= ">"; } return $open; } # ============================================================================ # Generate the element's close tag sub _elemclose { my $self = shift; my $elem = shift; my ($alias, $aliasdef); # Look for an alias $alias = $self->{alias}; if ( exists $alias->{$elem} ) { $aliasdef = $alias->{$elem}; $elem = $aliasdef->{elem}; } croak "Can't close an empty element" if ( $elem =~ m|[/_]$| ); return ""; } # ============================================================================ # Print an alias definition. This is called by show_table and show_alias sub _print_alias_defn { my $self = shift; my $defn = shift; my ($in, $out, $attr, $a) = @$defn; print "$in: "; print "<$out"; if ( ref $attr eq "HASH" ) { for $a ( keys %$attr ) { if ( defined $attr->{$a} ) { print " $a=\"$attr->{$a}\""; } else { print " $a"; } } } print ">\n"; } # ============================================================================ # Perform variable replacement sub _var_lookup { my $self = shift; my $name = shift; my @args; # If there is no variable function return a placeholder return "[VAR:$name]" if ( ! defined $self->{varfcn} ); # Split the arguments on comma - allow spaces @args = split /\s*,\s*/, $name; # Call the function return &{$self->{varfcn}} (@args); } # ============================================================================ return 1;