Code > Magazine Gallery component

Download Plaintext Version

Shared block

<%shared>
    my @months = qw(
        January  February  March      April    May       June
        July     August    September  October  November  December
    );

    my $attrs = $m->request_comp->attributes;

    my $newsletter_name = $attrs->{newsletter_name};
    my $content_root    = $attrs->{content_root};
    my $uri_root        = $attrs->{uri_root};
    my $comp_root       = $attrs->{comp_root};
    my $width           = $attrs->{width};
    my $height          = $attrs->{height};
    my $th_width        = $attrs->{th_width};
    my $th_height       = $attrs->{th_height};
</%shared>

Method: validate-request

<%method validate-request>
  <%args>
    $full_index
    @args
    $year
    $month
    $page
  </%args>

  <%init>
    # We should never get more than three dhandler args.

    $m->clear_and_abort(404) if scalar @args > 3;

    # Validate year/month/page as necessary.

    if (scalar @args > 0 && !exists $full_index->{$year})
        { $m->clear_and_abort(404); }
    if (scalar @args > 1 && !exists $full_index->{$year}{$month})
        { $m->clear_and_abort(404); }
    if (scalar @args > 2 && !exists $full_index->{$year}{$month}{$page})
        { $m->clear_and_abort(404); }

    # If a month was requested but a page was not, check for an OCRed version
    # of the issue.  It'll be named ocr.html.  If found, retrieve the issue
    # info in it.  It should have a method named issue-info that simply
    # returns the volume and number in plaintext.
    #
    # <%method issue-info>
    #     Volume 5 Number 1
    # </%method>

    my $issue_info;

    if (scalar @args == 2 && -f "$content_root/$year/$month/ocr.html") {
        if ($m->fetch_comp("$comp_root/$year/$month/ocr.html")->method_exists('issue-info')) {
            $issue_info = $m->scomp("$comp_root/$year/$month/ocr.html:issue-info");
        }
    }

    return $issue_info;
  </%init>
</%method>

Method: page-head

<%method page-head>
  <%args>
    @args
    $year
    $month
    $page
    $issue_info
  </%args>

% if (!scalar @args) {
    <% $newsletter_name %> Issue Years
% } elsif (scalar @args == 1) {
    <% $year %> <% $newsletter_name %> Issues
% } elsif (scalar @args == 2) {
    <% $months[$month - 1] %> <% $year %> <% $newsletter_name %> <% $issue_info %>
% } elsif (scalar @args == 3) {
%     (my $print_page = $page) =~ s/^0+//;  # strip leading 0 on page number
    <% $months[$month - 1] %> <% $year %> <% $newsletter_name %> Page <% $print_page %>
% }
</%method>

Method: navigation

<%method navigation>
  <%doc>
    We build a navigation bar that has links to all the years we have issues
    for, plus links for the months of the year we're currently viewing (if
    any.)
  </%doc>

  <%args>
    $full_index
    $year
    $month
  </%args>

    <div><a class="current-page" href="<% $uri_root %>/"><% $newsletter_name %> Index</a></div>

    <ul>
% for my $tmp_year (sort keys %$full_index) {
        <li>
            <a href="<% $uri_root %>/<% $tmp_year %>/"><% $tmp_year %></a>
            (<% scalar keys %{$full_index->{$tmp_year}} %> issues)
%     if ($tmp_year == $year) {
            <ul>
%         for my $tmp_month (sort keys %{$full_index->{$year}}) {
                <li>
                    <a href="<% $uri_root %>/<% $year %>/<% $tmp_month %>/"
                    class="sub1 <% $tmp_month == $month ? 'current-page' : '' %>">
                    <% $months[$tmp_month - 1] %></a>
                </li>
%         }
            </ul>
%     }
        </li>
% }
    </ul>
</%method>

Method: build-full-index

<%method build-full-index>
  <%doc>
    First look for the full index in the cache.  If not found, then build
    it and store it in the cache for later use.  This means that if we
    change anything on disk, the cache needs to be removed for changes to
    take effect.

    $full_index will be a large tree of hash references that defines the
    years, months, and pages we have.  Example:  $full_index->{1989}{01}{01}
    is the first page of the January 1989 issue.  This works nicely because
    we can simply numerically sort the keys at any level of the hash to get
    an ordered list of years, months, and/or pages.
  </%doc>

  <%args>
    $name => undef
  </%args>

  <%init>
    my $full_index = $m->cache->get($name || $newsletter_name);

    # Return if we had it cached, otherwise we proceed to build it and
    # cache it for the next time.
    if (defined $full_index) {
        return $full_index;
    }

    opendir(DIR, $content_root);
    my @years = sort { $a <=> $b }
                grep { /^19[89]\d$/ && -d "$content_root/$_" }
                readdir(DIR);
    closedir DIR;

    for my $year (@years) {
        opendir(DIR, "$content_root/$year");
        my @months = sort { $a <=> $b }
                     grep { /^\d\d$/ && -d "$content_root/$year/$_" }
                     readdir(DIR);
        closedir DIR;

        for my $month (@months) {
            opendir(DIR, "$content_root/$year/$month");
            my @pages = map { /^(\d\d)/; $1 }
                        sort { $a <=> $b }
                        grep { /^\d\d\.png$/ }
                        readdir(DIR);
            closedir DIR;

            for my $page (@pages) {
                $full_index->{$year}{$month}{$page} = 1;
            }
        }
    }

    $m->cache->set($newsletter_name => $full_index);

    return $full_index;
  </%init>
</%method>

Method: index-all

<%method index-all>
  <%doc>
    This method shows an index of all years.
  </%doc>

  <%args>
    $full_index
  </%args>

    <ul class="thumb-list">
% for my $year (sort keys %$full_index) {
%     my $first_month = (sort keys %{$full_index->{$year}})[0];
%     my $first_page  = (sort keys %{$full_index->{$year}{$first_month}})[0];
        <li>
            <a class="image-link" href="<% $year %>/"><img
                src="<% $year %>/<% $first_month %>/<% $first_page %>-th.png"
                width="<% $th_width %>" height="<% $th_height %>"
                alt="<% $year %> <% $newsletter_name %> Issues" /></a><br />
            <a href="<% $year %>/"><% $year %></a>
        </li>
% }
    </ul>
</%method>

Method: index-year

<%method index-year>
  <%doc>
    This method shows an index of all months in a year, showing the first
    page from each month.
  </%doc>

  <%args>
    $full_index
    $year
  </%args>

    <ul class="thumb-list">
% for my $month (sort keys %{$full_index->{$year}}) {
%     my $first_page = (sort keys %{$full_index->{$year}{$month}})[0];
        <li>
            <a class="image-link" href="<% $month %>/"><img
                src="<% $month %>/<% $first_page %>-th.png"
                width="<% $th_width %>" height="<% $th_height %>"
                alt="<% $months[$month - 1] %> <% $year %> <% $newsletter_name %>" /></a><br />
            <a href="<% $month %>/"><% $months[$month - 1] %></a>
        </li>
% }
    </ul>
</%method>

Method: index-month

<%method index-month>
  <%doc>
    This method shows an index of all pages in a month's magazine issue.  It
    also displays the OCRed version, if it exists, and builds a table of
    contents from it with links to the various sections of the document (as
    determined by header tags--it pays to create well-formed documents.)
  </%doc>

  <%args>
    $full_index
    $year
    $month
  </%args>

  <%init>
    my ($toc, $body);

    if (-f "$content_root/$year/$month/ocr.html") {
        $body = $m->scomp("$comp_root/$year/$month/ocr.html");
    }

    # The add-toc method returns the TOC and a modified body separately.

    if (defined $body) {
        ($toc, $body) = $m->comp('SELF:add-toc', year => $year,
            month => $month, body => $body);
    }
  </%init>

% if (defined $body) {
    <div id="side-thumb-list">
    <ul>
% } else {
    <ul class="thumb-list">
% }

% for my $page (sort keys %{$full_index->{$year}{$month}}) {
%     (my $print_page = $page) =~ s/^0+//;  # strip leading 0 on page number
        <li>
            <a class="image-link" href="<% $page %>/"><img src="<% $page %>-th.png"
                width="<% $th_width %>" height="<% $th_height %>"
                alt="<% $months[$month - 1] %> <% $year %> <% $newsletter_name %> Page <% $print_page %>" /></a><br />
            <a href="<% $page %>/">Page <% $print_page %></a>
        </li>
% }

% if (defined $body) {
    </ul>
    </div>
% } else {
    </ul>
% }

% if (defined $body) {
    <div id="toc_link">
        <p>
            [<a onclick="document.getElementById('toc_link').style.display = 'none';
                         document.getElementById('toc').style.display = 'block';"
                class="no-href">Show Table Of Contents</a>]
        </p>
    </div>

    <div style="display: none;" id="toc">
        <h2>Table Of Contents</h2>

        <% $toc %>
    </div>

    <% $body %>
% }
</%method>

Method: page

<%method page>
  <%doc>
    This method shows the full-sized image of a scanned page, with navigation
    links for going back/forward to other pages in the current issue.
  </%doc>

  <%args>
    $full_index
    $year
    $month
    $page
  </%args>

  <%init>
    (my $print_page = $page) =~ s/^0+//;  # strip leading 0 on page number
  </%init>

    <& SELF:page-navigation,
        uri_root => $uri_root, full_index => $full_index,
        year => $year, month => $month, page => $page &>

    <div class="bordered-image">
        <img width="<% $width %>" height="<% $height %>"
            src="<% $uri_root %>/<% $year %>/<% $month %>/<% $page %>.png"
            alt="<% $months[$month - 1] %> <% $year %> <% $newsletter_name %> Page <% $print_page %>" />
    </div>

    <& SELF:page-navigation,
        uri_root => $uri_root, full_index => $full_index,
        year => $year, month => $month, page => $page &>
</%method>

Method: page-navigation

<%method page-navigation>
  <%doc>
    This method generates first/back/forward/last links shown when browsing the
    scanned pages of a magazine issue.
  </%doc>

  <%args>
    $full_index
    $year
    $month
    $page
  </%args>

  <%init>
    my @pages = (sort keys %{$full_index->{$year}{$month}});
    (my $print_page  = $page)           =~ s/^0+//;  # strip leading 0
    (my $print_total = $pages[$#pages]) =~ s/^0+//;  # on page numbers
  </%init>

    <div class="page-navigation">

        <span>
% if ($page != 1) {
            <a href="<% $uri_root %>/<% $year %>/<% $month %>/<% $pages[0] %>/">&lt;&lt;</a>
% } else {
            &lt;&lt;
% }
        </span>

        <span>
% if ($page != 1) {
            <a href="<% $uri_root %>/<% $year %>/<% $month %>/<% $pages[$page - 2] %>/">&lt;
                Prev Page</a>
% } else {
            &lt; Prev Page
% }
        </span>

        <span>Page <% $print_page %> of <% $print_total %></span>

        <span>
% if ($page != scalar @pages) {
            <a href="<% $uri_root %>/<% $year %>/<% $month %>/<% $pages[$page] %>/">Next
                Page &gt;</a>
% } else {
            Next Page &gt;
% }
        </span>

        <span>
% if ($page != scalar @pages) {
            <a href="<% $uri_root %>/<% $year %>/<% $month %>/<% $pages[$#pages] %>/">&gt;&gt;</a>
% } else {
            &gt;&gt;
% }
        </span>
    </div>
</%method>

Method: add-toc

<%method add-toc>
  <%doc>
    This method accepts an HTML document body and creates a table of contents
    for it based on the headers in it.  A TOC (in the form of a properly-nested
    unordered list of links) and a modified document body (with headers altered
    to add unique IDs for those links to point to) are returned to the caller.
  </%doc>

  <%args>
    $year
    $month
    $body
  </%args>

  <%init>
    # First, the body is modified to add unique ID attributes to all header
    # tags.  The transformation is generally of this form:
    #
    #     Before: <h3>Foo Bar Baz</h3>
    #     After:  <h3 id="foo_bar_baz">Foo Bar Baz</h3>
    #
    # We store the IDs we create (foo_bar_baz), the text of the headers they
    # point to (Foo Bar Baz), and the header level number (3.)  That information
    # is used to build the TOC.

    my ($new_body, @toc_items, %ids);

    while ($body =~ /^(.*?)<h(\d)>(.+?)<\/h\2>(.*?)$/s) {
        # $prehead  - Eeverything before the header.  This is appeneded to
        #             $new_body on each iteration of this loop.
        # $head_num - The level of the header (1, 2, 3...)
        # $header   - The text of the header.
        # $posthead - Everything after the header.  $body is re-set to this on
        #             each iteration of this loop, otherwise the loop would
        #             never end.

        my ($prehead, $head_num, $header, $posthead) = ($1, $2, $3, $4);

        # Create the ID from the header text.  The ID will contain the first
        # five words of the text transformed and separated by underscores, plus
        # a number, if necessary, to make the ID unique.

        my $id =  lc($header);
        $id    =~ s/\<.+?\>//g;  # remove HTML (e.g. <br />)
        $id    =~ s/[^\w ]//g;   # remove non-word, non-space characters
        $id    =~ s/ +/ /g;      # collapse duplicate spaces
        $id    =  join('_', grep { $_ } (split(/ /, $id))[0 .. 4]);

        # In XHTML, it is invalid to start an ID with a number.  Prepend such
        # IDs with a single underscore.  We might as well handle the case where
        # $id is blank at this point, which happens with headers that have no
        # alphanumeric characters.

        if ($id =~ /^\d/ || $id eq '')
            { $id = "_$id"; }

        # Make sure ID is unique.  If necessary, append incrementing numbers to
        # a non-unique ID to make it unique.

        my ($tmp_id, $count) = ($id, 1);
        while ($ids{$tmp_id})
            { $tmp_id = $id . '_' . $count++; }
        $id       = $tmp_id;
        $ids{$id} = 1;

        # Now create the text for the TOC link from the header text.

        my $raw_link_text =  $header;
        $raw_link_text    =~ s/\<.+?\>//g; # remove HTML
        $raw_link_text    =~ s/[:\.]$//;   # remove some ugly trailing punctuation

        # Try to avoid ALL CAPS link text, but don't mangle words that aren't
        # already all-caps (e.g. MAGazine) or certain other acronyms that are
        # peculiar to our content.

        my $link_text;

        while ($raw_link_text =~ /(\S+)/g) {
            my $word = $1;

            if ($word eq uc($word) && $word !~ /^(MAG|M\.?C\.?U\.?C\.?|BBS|C\.?A\.?S\.?E\.?|MS-?DOS|I{2,})$/) {
                # ucfirst() is useless on words that don't start with letters.
                $word =~ /^([^[:alpha:]]+)?(.+)$/;
                $word =  $1 . ucfirst(lc($2));
            }

            $link_text .= "$word ";
        }

        push @toc_items, [ $head_num, $id, $link_text ];

        $new_body .= $prehead . qq{<h$head_num id="$id">$header</h$head_num>};

        $body = $posthead;
    }

    # Appending any remaining old body to new body.
    $new_body .= $body;

    # Build the TOC unordered list from the @toc_items array created above.
    # All the '    ' strings, etc. are part of a (vain?) attempt to generate
    # halfway readable HTML.

    my $last_level = 1;

    my $toc = "\n <ul>\n <li>$months[$month - 1] $year";

    for my $header (@toc_items) {
        my ($level, $id, $text) = @$header;

        my $list_item = ('    ' x ($level + 1)) . qq{<li><a href="#$id">$text</a>};

        # Create appropriate unordered list tags.

        if ($last_level == 0 || $last_level < $level) {
            $toc .= "\n" . ('    ' x $level) . "<ul>\n$list_item";
        } elsif ($last_level == $level) {
            $toc .= "</li>\n$list_item";
        } elsif ($last_level > $level) {
            $toc .= "</li>\n";

            while ($last_level != $level) {
                $toc .= ('    ' x $last_level) . "</ul>\n";
                $toc .= ('    ' x $last_level) . "</li>\n";
                $last_level--;
            }

            $toc .= $list_item;
        }

        $last_level = $level;
    }

    $toc .= "</li>\n";

    while ($last_level > 1) {
        $toc .= ('    ' x $last_level) . "</ul>\n" . ('    ' x $last_level) . "</li>\n";
        $last_level--;
    }

    $toc .= " </ul>\n";

    return ($toc, $new_body);
  </%init>
</%method>