Parent Directory
|
Revision Log
Revision 1871 - (view) (download) (as text)
| 1 : | sh002i | 1870 | ################################################################################ |
| 2 : | # WeBWorK Online Homework Delivery System | ||
| 3 : | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ | ||
| 4 : | sh002i | 1871 | # $CVSHeader: webwork-modperl/lib/WeBWorK/Template.pm,v 1.1 2004/03/10 02:31:05 sh002i Exp $ |
| 5 : | sh002i | 1870 | # |
| 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 : | sh002i | 1871 | use WeBWorK::Utils qw(readFile); |
| 104 : | sh002i | 1870 | |
| 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 : | sh002i | 1871 | my @result = $cg->$function({@args}); |
| 153 : | sh002i | 1870 | 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 |