[system] / trunk / webwork-modperl / lib / WeBWorK / Template.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork-modperl/lib/WeBWorK/Template.pm

Parent Directory Parent Directory | Revision Log 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