Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

I am writing to you about your approach for multiple similar pages. I am going to focus on the Monsters section of your site, because that is the part I've been looking at and am familiar with.

You have a section on Role Playing, with a subsection titled "Monsters". You've got a CSV file with all the information for all the monsters you want a page for.

Right now, it appears that each monster has its own Perl script. So there is "Dark Centaur.pl" for a dark centaur. And "Daemar.pl" for a Daemar and so forth. If you were to add a new monster you would add the monster to the CSV file and add a "New_Monster.pl" script.

What makes much more sense is to make one script that handles all monster related issues. Call it monsters.pl. Then when you want to access a list of all monsters, I've put together a CGI script that does exactly that. One script will handle all the monsters and also provide a summary page with links to detail view of specific monsters.

With this approach, to add a new monster, you simply add it to the CSV file. Everything else happens automatically.

Let me emphasize this key point; <bThere is no need to have a one-to-one relationship between pages displayed and scripts that generate them. A single script can produce many different pages.

Without any other messing about, here's a script that demonstrates how a single script can handle all your monsters.

use strict; use warnings; use CGI (); use URI::Escape; use Text::CSV; use Data::Dumper; use constant DATA_FILE => 'Monsters.csv'; use constant ATTRIBUTES => ( 'Climate/Terrain', 'Frequency', 'Organization', 'Activity cycle', 'Diet', 'Intelligence', 'Treasure', 'Alignment', 'No. Appearing', 'Armor Class', 'Movement', 'Hit Dice', 'THAC0', 'No. of Attacks', 'Damage/Attack', 'Special Attacks', 'Special Defenses', 'Magic Resistance', 'Size', 'Morale', 'XP Value', ); use constant SECTIONS => ( 'Appearance', 'Combat', 'Habitat/Society', 'Ecology', 'Variants', 'Note', ); use constant FIELDS => ( 'Monster', ATTRIBUTES, SECTIONS, ); use constant SUMMARY_FIELDS => ( 'Monster', 'Climate/Terrain', 'Frequency', 'Organization', 'Activity cycle', 'Diet', 'Intelligence', 'Treasure', 'Alignment', 'No. Appearing', 'Hit Dice', ); my $q = CGI->new; my @monsters = $q->param( 'name' ); print @monsters == 1 ? monster_detail($q, $monsters[0]) : monster_summary($q, @monsters ); # -------------------------------- sub page_start { # Generate the standard HTML for the start of a page. # Returns html text. my $q = shift; # CGI Object my $title = shift; # Title of the page. my $html = <<END_HTML; <html> <head> <title>$title</title> </head> <body> <h1>$title</h1> END_HTML return $html; } sub page_end { # Generate the standard HTML for the end of a page. # Returns html text. my $q = shift; # CGI Object my $url = $q->url(-relative=>1); my $html = <<END_HTML; <p><a href='$url'>Monster List</a></p> </body> END_HTML return $html; } sub monster_detail { # Format a monster detail page # Returns html text. my $q = shift; # CGI object my $monster = shift; # Name of monster to display my $monster_data = load_monsters( $monster ); return error_page( $q, "'$monster': Monster not found.") unless @$monster_data; my $html = join '', $q->header, page_start($q, $monster_data->[0]{Monster} ), gen_detail( $monster_data->[0], [ ATTRIBUTES ], [ SECTIONS ] ) +, page_end( $q ); return $html; } sub monster_summary { # Format a monster summary page. # Returns html text. my $q = shift; # CGI object my @monsters = @_; # List of monsters to summarize. my $monster_data = load_monsters( @monsters ); return error_page( $q, "No matching monsters found (@monsters)." ) unless @$monster_data; my $html = join '', $q->header, page_start($q, 'Monster Summary'), gen_table( $monster_data, [ SUMMARY_FIELDS ], { Monster => sub { my ($attr, $item) = @_; my $name = $item->{$attr}; my $esc = uri_escape( $name ); return "<a href='?name=$esc'>$name</a>"; }, }), page_end( $q ); return $html; } =head3 load_monsters Accepts a list of monster names to load. If no list is provided, all +monsters will be loaded from the data file. Returns an array ref containing hash refs, each containing data from o +ne monster. =cut sub load_monsters { my %wanted_monster; @wanted_monster{@_} = (); my $csv = Text::CSV_XS->new ({ binary => 1, quote_char => '~', sep_char => '|', }) or die "Error creating CSV parser: ".Text::CSV->error_diag; open my $fh, "<:encoding(utf8)", DATA_FILE or die "Error opening data file: $!"; my @monsters; while (my $row = $csv->getline ($fh)) { my %monster; @monster{ FIELDS() } = @$row; push @monsters, \%monster if !%wanted_monster || exists $wanted_monster{ $monster{Mo +nster} }; } $csv->eof or $csv->error_diag (); close $fh; return \@monsters; } sub gen_table { # Generate monster summary table. # Returns html text. my $data = shift; # Array of Monster hashes my $fields = shift; # Array of fields to use my $formatter = shift || {}; # Hash of subs used to preprocess +fields my $html = '<table><tr>'; $html .= join '', map "<th>$_</th>", @$fields; $html .= '</tr>'; for my $item ( @$data ) { $html .= '<tr>'; $html .= join '', map "<td>$_</td>", map { exists $formatter->{$_} ? $formatter->{$_}( $_, $item ) : $item->{$_}; } @$fields; $html .= '</tr>'; } $html .= '</table>'; } sub gen_detail { # Generate monster detail view # Returns html text. my $data = shift; my $attr = shift; my $sections = shift; my $html = '<dl>'; $html .= join '', map "<dt>$_</dt><dd>$data->{$_}</dd>", @$attr; $html .= '</dl>'; for my $section ( @$sections ) { my @paras = split /\\/, $data->{$section}; $html .= "<h2>$section</h2>"; $html .= join '', map "<p>$_</p>", @paras; } return $html; }

I know this code doesn't use your Base elements. I didn't want to download, configure and troubleshoot them. I thought it was more important to get this example together for you. You should be able to massage your standard infrastructure into this code.

As you do, be aware that it has got a big, fatal bug--I didn't define an error_page routine, you'll see the error if you specify a bogus monster as the name parameter. You'll need to define an error page routine, or replace it with one that already exists in your libraries.

The big theme of the discussion the other night was abstraction and generality. This is a good example of abstraction. Instead of one script for each monster, we have one script that can handle all the monsters by treating them as if they were identical. By doing so we can replace 20 scripts with one script.

Start by converting the monsters to this method. Then repeat with spell books and weapons. As you build similar scripts, you might find similarities that point to opportunities for code reuse. Even better you may be able to work on an even more abstract level and simplify your code even more.

First and foremost, you really need to move away from the SSI "one file per page" approach. Your life will be much simpler, and your stated goal of reducing your site size will be met.


TGI says moo


In reply to Re: Seeing Perl in a new light: Epilog by TGI
in thread Seeing Perl in a new light by Lady_Aleena

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (4)
As of 2024-03-28 15:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found