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} = "
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