[system] / trunk / webwork2 / lib / WeBWorK / Template.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/Template.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3973 - (download) (as text) (annotate)
Wed Jan 25 23:13:56 2006 UTC (7 years, 3 months ago) by sh002i
File size: 7236 byte(s)
forward-port from rel-2-2-dev: (update copyright date range -- 2000-2006.
this is probably overkill, since there are some files that were created
after 2000 and some files that were last modified before 2006.)

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/Template.pm,v 1.2 2004/03/10 02:51:57 sh002i Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::Template;
   18 use base qw(Exporter);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::Template - apply a template to a ContentGenerator.
   23 
   24 =head1 SYNOPSIS
   25 
   26  use WeBWorK::Template qw/template/;
   27 
   28  my $templateFile = "default.template";
   29  my $cg = WeBWorK::ContentGenerator::SomeSubclass->new($r);
   30 
   31  template($templateFile, $cg);
   32 
   33 =head1 DESCRIPTION
   34 
   35 WeBWorK uses templates to customize the presentation of pages. A template is a
   36 complete HTML document, containing normal HTML code and special escape
   37 sequences.
   38 
   39 =head2 ESCAPE SEQUENCES
   40 
   41 Escape sequences have a format similar to that of server-side includes (SSI).
   42 The format is as follows:
   43 
   44  <!--#NAME ARG1="VALUE1" ARG2="VALUE2" ...-->
   45 
   46 An escape's C<NAME> and arguments (C<ARG1>, C<ARG2>, etc.) are case-sensitive,
   47 the argument values may or may not be depending on the particular escape. Most
   48 escapes have case-sensitive values.
   49 
   50 Escape equences are replaced by dynamically generated content from WeBWorK's
   51 content generation system, WeBWorK::ContentGenerator. When a template escape
   52 C<NAME> is encountered in the document, the template processor checks for a
   53 method of the same name in the current content generator. If found, that method
   54 is invoked as follows:
   55 
   56  @result = $contentGenerator->NAME(\%escapeArguments);
   57 
   58 where %escapeArguments contains the key/value pairs of arguments in the escape
   59 sequence (like C<ARG1="VALUE1" ARG2="VALUE2">). The method may print() output
   60 directly to the client or return a result. If the method returns a non-empty
   61 value, it is sent to the client.
   62 
   63 =head2 CONDITIONAL PROCESSING
   64 
   65 In addition to the normal escape sequences above, the escape sequences C<#if>,
   66 C<#else>, and C<#endif> are reserved and used to conditionally include portions
   67 of the template in the output.
   68 
   69 The C<#if> escape sequence has the following form:
   70 
   71  <!--#if PRED1="VALUE1" PRED2="VALUE2" ...->
   72 
   73 When an C<#if> escape is evaluated, each predicate (C<PRED1>, C<PRED2>, etc.) is
   74 evaluated by calling a method named C<if_PRED> in the current content generator
   75 with the predicate's value as the sole argument. If no such method exists, the
   76 predicate is false. If the method returns a true value, the predicate is true.
   77 
   78 If any predicate is true, the code between the C<#if> escape and a matching
   79 C<#else> or C<#endif> escape is included in the output. C<#if> statements can be
   80 nested.
   81 
   82 For example:
   83 
   84  <!--#if loggedin="1"-->
   85  <!--#if can="loginstatus"-->
   86  <div class="LoginStatus">
   87   <!--#loginstatus-->
   88  </div>
   89  <!--#endif-->
   90  <!--#endif-->
   91  <!--#if can="path"-->
   92  <div class="Path">
   93   <!--#path style="text" image="/webwork2_files/images/right_arrow.png" text=" > "-->
   94  </div>
   95  <!--#endif-->
   96 
   97 Several predicate functions are defined in WeBWorK::ContentGenerator.
   98 
   99 =cut
  100 
  101 use strict;
  102 use warnings;
  103 use WeBWorK::Utils qw(readFile);
  104 
  105 our @EXPORT    = ();
  106 our @EXPORT_OK = qw(
  107   template
  108 );
  109 
  110 =head1 FUNCTIONS
  111 
  112 =over
  113 
  114 =item template($templatePath, $cg)
  115 
  116 Process the template file $templatePath. Methods from $cg, an instance of
  117 WeBWorK::ContentGenerator, are called to handle escape sequences in the
  118 template.
  119 
  120 =cut
  121 
  122 sub template {
  123   my ($templatePath, $cg) = @_;
  124 
  125   # the truth value of the top of this stack determines if we're printing output or not.
  126   # we want to start off in printing mode.
  127   # say $ifstack[-1] to get the result of the last <#!--if-->
  128   my @ifstack = (1);
  129 
  130   my @template = split /\n/, readFile($templatePath);
  131 
  132   foreach my $line (@template) {
  133     # This is incremental regex processing.
  134     # the /c is so that pos($line) doesn't die when the regex fails.
  135     while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) {
  136       my ($before, $function, $raw_args) = ($1, $2, $3);
  137       my @args = ($raw_args =~ /\S/) ? cook_args($raw_args) : ();
  138 
  139       if ($ifstack[-1]) {
  140         print $before;
  141       }
  142 
  143       if ($function eq "if") {
  144         # a predicate can only be true if everything else on the ifstack is already true, for ANDing
  145         push @ifstack, (if_handler($cg, [@args]) && $ifstack[-1]);
  146       } elsif ($function eq "else" and @ifstack > 1) {
  147         $ifstack[-1] = not $ifstack[-1];
  148       } elsif ($function eq "endif" and @ifstack > 1) {
  149         pop @ifstack;
  150       } elsif ($ifstack[-1]) {
  151         if ($cg->can($function)) {
  152           my @result = $cg->$function({@args});
  153           if (@result) {
  154             print @result;
  155           } else {
  156             warn "Template escape $function returned an empty list.";
  157           }
  158         }
  159       }
  160     }
  161 
  162     if ($ifstack[-1]) {
  163       print substr($line, (defined pos $line) ? pos $line : 0), "\n";
  164     }
  165   }
  166 }
  167 
  168 # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns
  169 # a list which pairs into key/values and fits nicely in {}s.
  170 #
  171 sub cook_args($) { # ... also used by bin/wwdb, so watch out
  172   my ($raw_args) = @_;
  173   my @args = ();
  174 
  175   # Boy I love m//g in scalar context!  Go read the camel book, heathen.
  176   # First, get the whole token with the quotes on both ends...
  177   while ($raw_args =~ m/\G\s*(\w*)="((?:[^"\\]|\\.)*)"/g) {
  178     my ($key, $value) = ($1, $2);
  179     # ... then, rip out all the protecty backspaces
  180     $value =~ s/\\(.)/$1/g;
  181     push @args, $key => $value;
  182   }
  183 
  184   return @args;
  185 }
  186 
  187 # This is different.  It probably shouldn't print anything (except in debugging cases)
  188 # and it should return a boolean, not a string.  &if is called in a nonstandard way
  189 # by &template, with $args as an arrayref instead of a hashref.  this is a hack!  yay!
  190 #
  191 # OK, this is a pluggin architecture.  it iterates through attributes of the "if" tag,
  192 # and for each predicate $p, it calls &if_$p in an object-oriented way, continuing the
  193 # grand templating theme of an object-oriented pluggable architecture using ->can($).
  194 #
  195 sub if_handler {
  196   my ($cg, $args) = @_;
  197   # A single if "or"s it's components.  Nesting produces "and".
  198 
  199   my @args = @$args; # Hahahahaha, get it?!
  200 
  201   if (@args % 2 != 0) {
  202     # flip out and kill people, but do not commit seppuku
  203     print '<!--&if recieved an uneven number of arguments.  This shouldn\'t happen, but I\'ll let it slide.-->\n';
  204   }
  205 
  206   while (@args > 1) {
  207     my ($key, $value) = (shift @args, shift @args);
  208 
  209     # a non-existent &if_$key is the same as a false result, but we're ORing, so it's OK
  210     my $sub = "if_$key"; # perl doesn't like it when you try to construct a string right in a method invocation
  211     if ($cg->can("if_$key") and $cg->$sub("$value")) {
  212       return 1;
  213     }
  214   }
  215 
  216   return 0;
  217 }
  218 
  219 =back
  220 
  221 =cut
  222 
  223 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9