Updated the blosxom homepage and mailing list URL
[matthijs/upstream/blosxom-plugins.git] / general / interpolate_fancy
1 # Blosxom Plugin: interpolate_fancy
2 # Author: Rael Dornfest <rael@oreilly.com>, 
3 # Modified by: Matthijs Kooijman <m.kooijman@student.utwente.nl>
4 # Version: 2006-01-14
5 # Documentation: See the bottom of this file or type: 
6 # perldoc interpolate_fancy
7
8 package interpolate_fancy;
9
10 # --- Configurable variables -----
11
12 # Do you want me to recursively interpolate into the story $title
13 # and $body?  Consider and reconsider carefully before turning this
14 # on as if anyone other than you has the ability to post stories,
15 # there's a chance of some tomfoolery, exposing variables and calling
16 # actions/subroutines you might not want called.
17 # 0 = No (default),  1 = Yes
18 our $recurse_into_story = 0;
19
20 # --------------------------------
21
22 sub start {
23   1;
24 }
25
26
27 # (Recursively) resolve conditinal includes <?...>...</?>.
28 # Expects two arguments: The (part of the) template we are expanding and
29 # wether or not this part will be displayed. When this last argument is false,
30 # this part of the template will not be displayed, but is still parsed to
31 # properly resolve nested conditions.
32 #
33 # This will resolve and substitute all conditionals on the "current level".
34 # This effectively means, the routine will search for and resolve all tags it
35 # finds (with proper nesting), until it finds a closing tag (</?>) for which
36 # it has not seen the opening tag, or there are no more opening tags in the
37 # file.
38
39 # This function will return the template with all conditionals on the current
40 # level resolved. This will guarantee that if there are any opening or closing
41 # tags left in the template, the first one will be a closing tag (which can
42 # then be resolved in the upper level).
43 sub _resolve_nested {
44     my $template = shift;
45     my $display  = shift;
46   
47     while (1) {
48         if ($template !~ /(.*?)<\?(\!?\$\w+(?:::)?\w*)(?:\s+?(.+?))?>(.*)/s) {
49             return $template; # No open tags, normal text
50         }
51        
52         my $head = $1;
53         my $var  = $2;
54         my $attr = $3;
55         my $tail = $4;
56
57         if ($head =~ /<\/\?>/s) {
58             return $template; # There is a closing tag before the first open tag,
59                              # we're done. 
60         }
61         
62         $displayitem = $display;
63         if ($displayitem) { # Don't care about these tests if we're not displayed anyway.
64             my $negated = ($var =~ s/^\!//); # Remove negation mark
65             if ($negated || !$attr) {
66                 if ($attr) {
67                     warn("<?!$var $attr>: Negated expression can't have attributes, ignoring them.");
68                 }
69                 $displayitem = eval("defined $var");
70                 if ($negated) { $displayitem = !$displayitem; }
71             } else {
72                 $displayitem = _test(eval($var), $attr);
73             }
74         }
75
76
77         $tail = _resolve_nested($tail, $displayitem);
78
79         if ($tail !~ /<\/\?>/s) { # Is there no closing tag?
80             warn("Invalid nesting: <?$var $attr> missing closing tag.");
81         }
82         if ($displayitem)
83         {
84             $tail =~ s/<\/\?>//s; # Remove the closing tag
85         } else {
86             $tail =~ s/.*?<\/\?>//s; # Remove up to the closing tag
87         }
88         $template = $head . $tail;
89     }
90     return $template;
91 }
92
93
94 sub interpolate {
95   return sub {
96
97     package blosxom;
98
99     # Halt recursive interpolation of story $body
100     # by mangling interpolation tags (to be unmangled in a moment)
101     unless ($recurse_into_story) {
102       $blosxom::title =~ s/<(\@|\??\$)/<#INTERPOLATE_FANCY_DEFANG#$1/gsi;
103       $blosxom::body =~ s/<(\@|\??\$)/<#INTERPOLATE_FANCY_DEFANG#$1/gsi;
104     }
105
106     my $template = shift;
107
108     # Backward Compatibility with core Blosxom style interpolation
109     $template =~ s#(?<!<)(?<!<\?)(?<!<\?!)(\$\w+(?:::)?\w*)#<$1 />#gs; 
110
111     #
112     # Conditional inclusion
113     #
114     # e.g. <?$path>stuff</?>
115     $template = interpolate_fancy::_resolve_nested($template, 1);
116
117     #
118     # Variable expansion
119     #
120     # e.g. <$var />
121     while( $template =~ s/<\$([a-zA-Z?]\w+(?:::)?\w*)\s+?\/>/"defined \$$1 ? \$$1 : undef"/gsee ) { }
122
123     #
124     # Actions 
125     #
126     # e.g. <@plugin.subroutine arg1="a" output="no" />
127     # e.g. <@plugin.subroutine encoding="Latin1" output="yes">pass content</@> 
128     $template =~ s#<\@(\w+?)\.(\w+?)\s+?(.+?)?(?:>(.*?)<\/\@\1\.\2>|\s+?\/>)#&interpolate_fancy::_action($1,$2,$3,$4)#gse;
129
130     # Unmangle mangled interpolation tags in story $title and $body
131     # (by now in the template itself)
132     unless ($recurse_into_story) {
133       $template =~ s/<#INTERPOLATE_FANCY_DEFANG#(\@|\??\$)/<$1/gsi;
134     }
135
136     return $template;
137
138   };  
139 }
140
141 sub _test {
142   my($variable, $attr) = @_;
143   my $attributes = interpolate_fancy::_attributes($attr);
144
145   defined $attributes->{eq} and return $variable eq $attributes->{eq};
146   defined $attributes->{ne} and return $variable ne $attributes->{ne};
147   defined $attributes->{gt} and return $variable > $attributes->{gt};
148   defined $attributes->{lt} and return $variable < $attributes->{lt};
149   defined $attributes->{like} and return $variable  =~ /$attributes->{like}/;
150   defined $attributes->{unlike} and return $variable  !~ /$attributes->{unlike}/;
151
152   return 0;
153 }
154
155 sub _action {
156   my($plugin, $action, $attr, $content) = @_;
157
158   $content =~ s#<\@(\w+?)\.(\w+?)\s+?(.+?)?(?:>(.*?)<\/\@\1\.\2>|\s+?\/>)#&interpolate_fancy::_action($1,$2,$3,$4)#gse;
159
160   my $attributes = interpolate_fancy::_attributes($attr);
161   
162   $blosxom::plugins{$plugin} 
163     and $plugin->can($action) 
164       and $result = $plugin->$action($attributes, $content);
165
166   return $attributes->{'output'} =~ /yes/i ? $result : undef;
167 }
168
169 sub _attributes {
170   my $attr = shift;
171
172   my $attributes = {};
173   while ( $attr =~ /\b(\w+?)\s*?=\s*?(["'])(.*?)\2/g ) {
174     $attributes->{$1} = $3;
175   }
176
177   return $attributes;
178 }
179
180 1;
181
182 __END__
183
184 =head1 NAME
185
186 Blosxom Plug-in: interpolate_fancy
187
188 =head1 SYNOPSIS
189
190 Overrides Blosxom's far simpler to use, but more limited, default interpolate() 
191 subroutine.
192
193 Include bits of text and template variable values in templates, either 
194 conditionally or unconditionally:
195
196 Perform actions (i.e. call plug-in subroutines) at any point in your page, 
197 whether to act on current content and return results or no.
198
199 =head2 Variable expansion
200   This syntax will expand to the value of the referenced variable and can be
201   used to include dynamic content in your pages.
202
203   * Unconditionally and recursively
204
205     e.g. include a link to the story's path using various template variables.
206
207     <a href="<$url /><$path />"><$path /></a>
208
209     Limited by the $recurse_into_story configuration directive (see
210     the CONFIGURATION below).
211
212   * Unconditionally and recursively (backward compatibility with Blosxom's standard interpolation)
213
214     e.g. include a link to the story's path using various template variables.
215
216     <a href="$url$path">$path</a>
217
218     Limited by the $recurse_into_story configuration directive (see
219     the CONFIGURATION below).
220
221 ==head2 Conditional inclusion
222   These tags will each have a certain condition attached to them, depending on
223   what syntax is used exactly. All text between the opening and closing tags
224   will only be included in the final output of the page if the given condition
225   is true, it will be discarded otherwise.
226
227   Note that as of 2006-01-11 conditional tags can be nested, which effectively
228   allows you to specify the logical and of two (or more) conditions. There is
229   no way yet to specify the logical or, though. If you're using nested
230   conditions and they won't work properly, check your error log, since the
231   plugin puts warnings about incorrect nestings there.
232
233   * The template variable has a value (i.e. is defined)
234
235     e.g. include a hyperlink to the story's path if it has a path (i.e.
236     $path is defined).
237
238     <?$path><a href="<$url /><$path />"><$path /></a></?>
239
240   * The template variable doesn't have a value (i.e. is NOT defined)
241
242     e.g. include a hyperlink to home if path is undefined.
243
244     <?!$path><a href="<$url />">home</a></?>
245
246   * The template variable is equal (=) to a particular value
247
248     e.g. include "1 writeback" (singular) if the value of writeback count is 1
249
250     <$writeback::count /> <?$writeback::count eq="1">writeback</?>
251
252   * The template variable is not equal (!=) to a particular value
253
254     e.g. include "x writebacks" (plural) if the value of writeback 
255          count (x) is not 1
256
257     <$writeback::count /> <?$writeback::count ne="1">writebacks</?>
258
259   * The template variable is less than (<) a particular value
260
261     e.g. include "no writebacks" if the value of writeback count is < 1
262
263     <?$writeback::count lt="1">no writebacks</?>
264
265   * The template variable is greater than (>) a particular value
266
267     e.g. include "oodles of writebacks" if the value of writeback count is > 50
268
269     <?$writeback::count gt="50">oodles of writebacks</?>
270
271   * The template variable is like (=~) a particular regular expression
272
273     e.g. Greet a visitor properly, depending on their courtesy title
274
275     Howdy, 
276     <?$user::courtesy like="^(Mr\.?|Sir)$">Sir</?>
277     <?$user::courtesy like="^(Mr?s\.?|Miss)$">M'am</?>
278
279   * The template variable is unlike (!~) a particular regular expression
280
281     e.g. The posting is neither a film nor a book
282
283     <?$path unlike="/(Film|Literature)">no review</?>
284
285 =head2 Actions
286
287 Perform an action (i.e. call a plug-in's subroutine) at any point in your page.
288 Optionally pass arguments as key/value pairs stated as XML-style attributes.
289 For example: 
290
291   <@plugin.subroutine arg1="a" arg2="bee" />
292
293 calls &plugin::subroutine( {'arg1'=>'a', 'arg2'=>'bee' } ).
294
295 Specify that results should be sent to the browser using the output="yes" 
296 attribute like so:
297   
298   <@plugin.subroutine arg1="a" arg2="bee" output="yes" />
299
300 Otherwise, subroutines will still have their effect, but the results will 
301 be tossed out.
302
303 Content wrapped in the action call is sent as another argument to the 
304 subroutine:
305
306   <@plugin.subroutine encoding="Latin1" output="yes">
307   pass this content
308   </@plugin.subroutine> 
309
310 This calls &plugin::subroutine( {'encoding'=>'Latin1', 'output'=>'yes' }, "pass this content" ).
311
312 Actions are recursive, meaning that you can embed an action inside another, 
313 and so on and so on and so on.  Actions are unfolded from the inside out,
314 with the most deeply embedded first, second deepest second, and so forth until
315 the outermost action is performed last.
316
317 Recursion is limited by the $recurse_into_story configuration directive (see
318 the CONFIGURATION below).
319
320 =head3 An Example
321
322 For those of you interested in writing plugin actions or using some of the
323 more advanced features in your Blosxom blog templates, here are a couple of
324 sample actions:
325
326 --
327
328 # For the sake of this example, assume these actions live in a "myplugin"
329 # plugin
330
331 # This action strips HTML from its content
332 sub strip_html {
333   my($self, $attributes, $content) = @_;
334   $content =~ s!</?.+?>!!g;
335   return $content;
336 }
337
338 # This action foreshortens its content to a length specified in the call to
339 # action's length attribute
340 sub foreshorten {
341   my($self, $attributes, $content) = @_;
342   my $default_length = 144;
343   return substr($content, 0, $attributes{'length'}||$default_length);
344 }
345
346 --
347
348 Calling these individually in a Blosxom flavour template looks something like:
349
350 The following bit of text is devoid of HTML:
351 <@myplugin.strip_html output="Yes">
352 Silly <a href="http://www.raelity.org/">me</a>, I plumb 
353 <em>forgot</em> to remove the HTML.
354 </@myplugin.strip_html>
355
356 The following bit of text is only 20 characters in length:
357 <@myplugin.foreshorten output="Yes">
358 This text is far longer than 20 characters on the page, 
359 but will only appear as "This text is far lon" in the
360 resulting page.
361 </@myplugin.foreshorten>
362
363 And in combination, stripping the HTML _before_ foreshortening (notice
364 the strip_html action is embedded inside the foreshorten action and
365 thus is run first):
366
367 The following bit of text is only 20 characters in length and devoid of HTML:
368 <@myplugin.foreshorten output="Yes">
369 <@myplugin.strip_html output="Yes">
370 Silly <a href="http://www.raelity.org/">me</a>, I plumb 
371 <em>forgot</em> to remove the HTML.
372 This text is far longer than 20 characters on the page, 
373 but will only appear as "This text is far lon" in the
374 resulting page.
375 </@myplugin.strip_html>
376 </@myplugin.foreshorten>
377
378 =head1 INSTALLATION
379
380 Drop the interpolate_fancy plug-in into your Blosxom plugins folder.
381
382 =head1 CONFIGURATION
383
384 None necessary; interpolate_fancy will run out of the box with no need
385 of additional configuration or fiddling on your part (caveat: see 
386 BACKWARD COMPATILITY below).
387
388 The interpolate_fancy plugin does sport one configuration directive
389 which you should very much consider leaving alone.  
390
391 # 0 = No (default),  1 = Yes
392 my $recurse_into_story = 0;
393
394 $recurse_into_story decides whether or not the interpolation engine 
395 should respect and interpolate (swap for the associated value) 
396 variables and actions embedded in story $title and $body themselves.
397
398 Off by default, you should consider and reconsider carefully before 
399 turning this on as if anyone other than you has the ability to post 
400 stories to your blog, there's a chance of some tomfoolery, exposing 
401 variables and calling actions/subroutines you might not want called.
402
403 =head1 BACKWARD COMPATIBILITY
404
405 If you've been using core Blosxom's interpolation style 
406 (e.g. $title), this plugin will provide backward compatibility,
407 requiring no template rewriting on your part.
408
409 If you've been using the interpolate_conditional plugin,
410 the conditional bits won't be respected by default.  You should
411 run your templates through the interpolate2fancy utility
412 [http://www.blosxom.com/downloads/utilities/interpolate2fancy.py].
413
414 =head1 VERSION
415
416 2006-01-11
417
418 =head1 AUTHOR
419
420 Rael Dornfest  <rael@oreilly.com>, http://www.raelity.org/
421 Modified by Matthijs Kooijman <m.kooijman@student.utwente.nl>, http://katherina.student.utwente.nl/~matthijs/blog
422
423 This plugin is now maintained by the Blosxom Sourceforge Team,
424 <blosxom-devel@lists.sourceforge.net>.
425
426 =head1 SEE ALSO
427
428 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
429
430 Blosxom Plugin Docs: http://blosxom.sourceforge.net/documentation/users/plugins.html
431
432 =head1 BUGS
433
434 None known; please send bug reports and feedback to the Blosxom
435 development mailing list <blosxom-devel@lists.sourceforge.net>.
436
437 =head1 LICENSE
438
439 Blosxom and this Blosxom Plug-in
440 Copyright 2003, Rael Dornfest 
441
442 Permission is hereby granted, free of charge, to any person obtaining a
443 copy of this software and associated documentation files (the "Software"),
444 to deal in the Software without restriction, including without limitation
445 the rights to use, copy, modify, merge, publish, distribute, sublicense,
446 and/or sell copies of the Software, and to permit persons to whom the
447 Software is furnished to do so, subject to the following conditions:
448
449 The above copyright notice and this permission notice shall be included
450 in all copies or substantial portions of the Software.
451
452 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
453 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
454 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
455 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
456 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
457 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
458 OTHER DEALINGS IN THE SOFTWARE.