package Weblibs; $VERSION = "1.00"; =head1 NAME Weblibs.pm - Fill-in-the-blanks funnythings for the Web =head1 AUTHOR David M. Chess, weblibs@davidchess.com =head1 SYNOPSIS use Weblibs; # Initialize the titles and bodies (in practice, # these would be considerably longer!) $libarray = [ { hinttitle => "Important Words", fulltitle => "Proverbs", body => "These are [plural noun] that try men's [plural noun].
A [noun] saved is a penny [verb, past tense].
Too many [plural noun] [verb] the [food item]." }, { hinttitle => "Poem", fulltitle => "That thing about ships", body => "I must go down to the [noun] again,
To the [adjective] sea and the [noun].
All I [verb] is a tall [noun],
And a [noun] to [verb] her by." }]; # Create a new set of Libs from it $ls = new Weblibs::Libset($libarray); # Tell it where the script is $ls->{scripturl} = "WeblibDriver.cgi"; # Do a little tailoring $ls->{menutitle} = "Fred's Weblibs Page"; $ls->{inputtitle} = "Fred's Weblibs -- &hinttitle;"; $ls->{resulttitle} = "&fulltitle;"; $ls->{result_intro_text} = "Here's what you've created!

\n"; $ls->{inputbuttontext} = "GO!"; # Print whatever the current CGI state calls for print $ls->getCgiPage(); =head1 USAGE The Weblibs package provides a Web/CGI-based interface to a set of amusing things, similar to the amusing things that constitute the popular "Mad Libs" paper-and-pencil party game. ("Lib", here, is pronounced to rhyme with "bib", and has no connection to the use of "lib", generally pronounced so as to rhyme with "vibe", and used as shorthand for "library".) The Weblibs user is first presented with a menu of available Libs, described by vague and general descriptions, and asked to choose one. Upon choosing one, a list of parts of speech is presented, and the user prompted to give a word for each part of speech. Finally, the full filled-in Lib is shown to the user, with its actual title, and the user's chosen words filled into critical places in the text. The result is always amusing. =head1 HISTORY 1999/09/11 - First release version, 1.00 =head1 LIMITATIONS The author is too lazy to document all the different instance variables that you can mess with to tailor the html output. See the code for the "new" routine to see them all being initialized. =cut use strict; 1; =head1 Details =head2 Setting up =head3 new($list_reference) creates a new Weblibs::Libset object, associated with a given set of Weblibs. The set is passed as an array of hash references. Each hash has fields "hinttitle", "fulltitle", and "body". The "hinttitle" is used on the menu page to give the user some vague idea of the Libs available; the "fulltitle" is shown on the final results page, as the actual title of the final thing. The "body" field consists of the text of the Lib, with the "blanks" represented by part-of-speech names in square brackets. See the SYNOPSIS. The "body" field may also contain HTML markup if desired. The new method returns, of course, a new Weblibs::Libset, or undef if error (currently there is no such thing as an error). =head2 The scripturl instance variable =head3 $object->{scripturl} = "http://www.whatever.com/path/whatever.ext"; This instance variable must be set to the URL of the script itself, before any methods are called on the new object, so that the HTML forms produced will cause the script to be called again when the user completes the form. =cut # # # new # # # sub Weblibs::Libset::new { my $class = shift; my $self = {}; bless $self, $class; my $tref; if (ref $_) { $tref = shift; } else { $tref = \@_; } $self->{libs} = $self->_process_libs($tref); $self->{menubuttontext} = "OK"; $self->{menutitle} = "Weblibs Menu"; $self->{bgcolor}="#ffffff"; $self->{textcolor}="#000000"; $self->{background} = ""; $self->{scripturl} = "http://you.forgot.the.url/"; $self->{amp} = "&"; $self->{menu_page} = <<"EOD"; &menu_prolog; &menu_form; &credits; &menu_epilog; EOD $self->{menu_prolog} = <<"EOD"; &menutitle; &standard_prolog;

&menutitle;

&menu_intro_text; EOD $self->{menu_intro_text} = <<"EOD"; The following Weblibs are available: EOD $self->{menu_form_prolog} = "

"; $self->{menu_form_epilog} = "

"; $self->{menu_epilog} = "&standard_epilog;"; $self->{menu_form} = <<"EOD"; &menu_form_prolog; &menu_lines; &menu_form_epilog; EOD $self->{menu_line} = <<"EOD"; &hinttitle;
EOD $self->{input_page} = <<"EOD"; &input_prolog; &input_form; &credits; &input_epilog; EOD $self->{inputbuttontext} = "OK"; $self->{inputtitle} = "Weblibs -- &hinttitle;"; $self->{input_prolog} = <<"EOD"; &inputtitle; &standard_prolog;

&inputtitle;

&input_intro_text; EOD $self->{input_intro_text} = <<"EOD"; Fill in all the blanks as indicated, and press "&inputbuttontext;".

EOD $self->{input_form_prolog} = <<"EOD";

EOD $self->{input_form_epilog} = <<"EOD";

EOD $self->{input_epilog} = "&standard_epilog;"; $self->{input_form} = <<"EOD"; &input_form_prolog; &input_lines; &input_form_epilog; EOD $self->{input_line} = <<"EOD"; &pos;: EOD $self->{result_page} = <<"EOD"; &result_prolog; &result_body; &credits; &result_epilog; EOD $self->{result_prolog} = <<"EOD"; &resulttitle; &standard_prolog;

&fulltitle;

&result_intro_text; EOD $self->{resulttitle} = "Weblibs -- &fulltitle;"; $self->{result_intro_text} = "

\n"; $self->{result_epilog} = "&standard_epilog;"; $self->{credits} = <<"EOD";


Weblibs were inspired by Mad Libs(tm), a product of Price Stern Sloan. The Weblibs script is available at DavidChess.com, which is not affiliated with Price Stern Sloan. EOD $self->{standard_prolog} = ""; $self->{standard_epilog} = "\n"; $self->{term_prefix} = ""; $self->{term_suffix} = ""; return $self; } =head2 Instance Methods =head3 $stuff_to_print = $object->getCgiPage() The getCgiPage() method returns the stuff that a CGI script using the module would normally return, given the current contents of STDIN, %ENV, and so on. If the script was invoked directly from an HTML page, the menu of Libs will be presented; if it was invoked by the user selecting an individual lib, the fill-in-the-blanks page for that lib will be presented; and if the script was invoked by the user returning the fill-in-the-blanks form for a lib, the filled-in result will be presented. The returned value will include the usual CGI "Content-type: text/html\n\n" at the top, so a normal CGI script needs to do nothing but print it (see the SYNOPSIS). Of course, a CGI script using this method must not touch STDIN before calling it, since it will be looking there for the form contents. =cut # # # getCgiPage # # # sub Weblibs::Libset::getCgiPage { my $self = shift; $self->{formhash} = $self->_decode_form(); my $c = "Content-type: text/html\n\n"; $c="" if not defined $ENV{REMOTE_ADDR}; # not really a CGI if ($self->{formhash}->{stage} == 0) { return $c.$self->getMenuPage(); } elsif ($self->{formhash}->{stage} == 1) { return $c.$self->getInputPage($self->{formhash}->{n}); } else { my $wordsref = $self->_parse_cgi_words(); return $c.$self->getResultPage($self->{formhash}->{n},$wordsref); } } =head3 $menu_page_html = $object->getMenuPage() returns the HTML (with no CGI prolog) for the menu page associated with the Libset. A normal CGI script will not have to use this (use getCgiPage() instead), but here it is anyway. =cut # # # getMenuPage # # # sub Weblibs::Libset::getMenuPage { my $self = shift; return $self->_expand("&menu_page;"); } =head3 $input_page_html = $object->getInputPage($n) returns the HTML (with no CGI prolog) for the fill-in-the-blanks form associated with the nth lib in the Libset. A normal CGI script will not have to use this (use getCgiPage() instead), but here it is anyway. =cut # # # getInputPage # # # sub Weblibs::Libset::getInputPage { my $self = shift; my $n = shift; return $self->_expand("&input_page;",$n); } =head3 $input_result_html = $object->getResultPage($n,$arrayref) returns the HTML (with no CGI prolog) for the result of filling in the nth lib in the Libset with the words in the given (referenced) array, in the obvious order. A normal CGI script will not have to use this (use getCgiPage() instead), but here it is anyway. =cut # # # getResultPage # # # sub Weblibs::Libset::getResultPage { my $self = shift; my $n = shift; my $words = shift; return $self->_expand("&result_page;",$n,$words); } # # # _expand # # # sub Weblibs::Libset::_expand { my $self = shift; my $data = shift; my $libnum = shift; my $wordsref = shift; for (;;) { $self->{changed} = 0; $data =~ s/&([^;]*);/$self->_dosub($1,$libnum,$wordsref)/ge; last if not $self->{changed}; } return $data; } # # # _dosub # # # sub Weblibs::Libset::_dosub { my $self = shift; my $keyword = shift; my $libnum = shift; my $wordsref = shift; $self->{libnum} = $libnum; if ($keyword eq "backattrib") { $self->{changed}++; return $self->{background} ? " background=$self->{background} " : ""; } if ($keyword eq "hinttitle") { $self->{changed}++; return $self->{libs}->[$libnum]->{hinttitle}; } if ($keyword eq "fulltitle") { $self->{changed}++; return $self->{libs}->[$libnum]->{fulltitle}; } if ($keyword eq "pos") { $self->{changed}++; return $self->{libs}->[$libnum]->{pos}->[$self->{index}]; } if ($keyword =~ /^A(\d+)$/) { $self->{changed}++; return $self->{term_prefix}.$wordsref->[$1].$self->{term_suffix}; } if ($keyword eq "result_body") { $self->{changed}++; return $self->{libs}->[$libnum]->{body}; } if ($keyword eq "menu_lines") { my $answer = ""; foreach (0..$#{$self->{libs}}) { $self->{index} = $_; $answer .= $self->_expand("&menu_line;",$_); } return $answer; } if ($keyword eq "input_lines") { my $answer = ""; foreach (0..$#{$self->{libs}->[$libnum]->{pos}}) { $self->{index} = $_; $answer .= $self->_expand("&input_line;",$libnum); } $self->{changed}++; return $answer; } if (defined $self->{$keyword}) { $self->{changed}++; return $self->{$keyword}; } return "&$keyword;"; } # # # _process_libs # # # sub Weblibs::Libset::_process_libs { my $self = shift; my @answer; my $ref = shift; foreach (@$ref) { my $h = {}; $h->{pos} = []; push @answer, $h; $h->{hinttitle} = $_->{hinttitle}; $h->{fulltitle} = $_->{fulltitle}; foreach ( $_->{body} =~ /\[([^\]]*)\]/g ) { push @{$h->{pos}}, $_; } my $i = 0; $h->{body} = $_->{body}; $h->{body} =~ s/\[[^\]]*\]/"&A".$i++.";"/ge; } return \@answer; } # # # _decode_form # # # sub Weblibs::Libset::_decode_form { my $self = shift; my @form_input = ; chomp @form_input; my $answer = parse_stuff({},(join " ",@form_input)); # or ""? my $query = defined($ENV{QUERY_STRING}) ? $ENV{QUERY_STRING} : ""; $answer = parse_stuff($answer,$query); $answer->{stage} = 0 if not defined $answer->{stage}; return $answer; } sub parse_stuff { my $answer = shift; my ($key,$value); my @input = split /&/ , shift(); foreach (@input) { ($key,$value) = (/(.*)=(.*)/); $answer->{lc $key} = fix_url_encoding($value); } return $answer; } sub fix_url_encoding { my $string = shift; $string =~ s/\+/ /g; $string =~ s/\%([0-9A-Fa-f]{2})/pack("C",hex($1))/ge; return $string; } # # # _parse_cgi_words # # # sub Weblibs::Libset::_parse_cgi_words { my $self = shift; my $count = scalar @{$self->{libs}->[$self->{formhash}->{n}]->{pos}}; my $answer = []; foreach (0..($count-1)) { $answer->[$_] = defined($self->{formhash}->{$_}) ? $self->{formhash}->{$_} : " "; } return $answer; } =head2 Tailoring the generated HTML The HTML pages generated by the Weblibs module are generated by an absurdly flexible process of recursive expansion. For instance, the menu page is generated by expanding the symbol "&menu_page;", whose default value is &menu_prolog; &menu_form; &menu_epilog; The default expansion of &menu_prolog; is in turn &menutitle;

&menutitle;

&menu_intro_text; and so on and so on. See the code for the "new" method for the default expansions of most of the symbols. There are a few special cases: for instance, "&menu_lines;" is always expanded by expanding the symbol "&menu_line;" once for each available lib, "&input_lines;" is always expanded by expanding the symbol "&input_line;" once for each to-be-filled-in blank in the active lib, "&backattrib;" is expanded to "background=&background;" if the symbol "&background;" is set, and to null otherwise. See the internal _dosub() method for the other exceptions if you're wildly curious. To change how a symbol (except the special cases) is expanded, simply alter the corresponding instance variable before calling one of the getXxxPage() methods. See the SYNOPSIS for a few simple examples; experiment if you want to figure out more complex ones. =cut