tagging: Allow using titles in for related stories.
[matthijs/upstream/blosxom-plugins.git] / general / interpolate_fancy
index 1a7f21c21eb508a58e7677092b37f1f113dab985..ff888e15979c4f680ee0e3def297a77141859c76 100644 (file)
@@ -1,6 +1,8 @@
 # Blosxom Plugin: interpolate_fancy
-# Author(s): Rael Dornfest <rael@oreilly.com> 
-# Version: 2003-09-09:12:10
+# Author: Rael Dornfest <rael@oreilly.com>, 
+# Modified by: Matthijs Kooijman <m.kooijman@student.utwente.nl>
+# and Barijaona Ramaholimihaso
+# Version: 2007-09-27
 # Documentation: See the bottom of this file or type: 
 # perldoc interpolate_fancy
 
@@ -14,14 +16,85 @@ package interpolate_fancy;
 # there's a chance of some tomfoolery, exposing variables and calling
 # actions/subroutines you might not want called.
 # 0 = No (default),  1 = Yes
-my $recurse_into_story = 0;
+our $recurse_into_story = 0;
 
 # --------------------------------
 
+my $parsing_story = 0 ;
+
+
 sub start {
   1;
 }
 
+
+# (Recursively) resolve conditinal includes <?...>...</?>.
+# Expects two arguments: The (part of the) template we are expanding and
+# wether or not this part will be displayed. When this last argument is false,
+# this part of the template will not be displayed, but is still parsed to
+# properly resolve nested conditions.
+#
+# This will resolve and substitute all conditionals on the "current level".
+# This effectively means, the routine will search for and resolve all tags it
+# finds (with proper nesting), until it finds a closing tag (</?>) for which
+# it has not seen the opening tag, or there are no more opening tags in the
+# file.
+# 
+# This function will return the template with all conditionals on the current
+# level resolved. This will guarantee that if there are any opening or closing
+# tags left in the template, the first one will be a closing tag (which can
+# then be resolved in the upper level).
+sub _resolve_nested {
+    my $template = shift;
+    my $display  = shift;
+  
+    while (1) {
+        if ($template !~ /(.*?)<\?(\!?\$\w+(?:::\w+)*)(?:\s+?(.+?))?>(.*)/s) {
+            return $template; # No open tags, normal text
+        }
+       
+        my $head = $1;
+        my $var  = $2;
+        my $attr = $3;
+        my $tail = $4;
+
+        if ($head =~ /<\/\?>/s) {
+            return $template; # There is a closing tag before the first open tag,
+                             # we're done. 
+        }
+        
+        my $displayitem = $display;
+        if ($displayitem) { # Don't care about these tests if we're not displayed anyway.
+            my $negated = ($var =~ s/^\!//); # Remove negation mark
+            if ($negated || !$attr) {
+                if ($attr) {
+                    warn("<?!$var $attr>: Negated expression can't have attributes, ignoring them.");
+                }
+                $displayitem = eval("defined $var");
+                if ($negated) { $displayitem = !$displayitem; }
+            } else {
+                $displayitem = _test(eval($var), $attr);
+            }
+        }
+
+
+        $tail = _resolve_nested($tail, $displayitem);
+
+        if ($tail !~ /<\/\?>/s) { # Is there no closing tag?
+            warn("Invalid nesting: <?$var $attr> missing closing tag.");
+        }
+        if ($displayitem)
+        {
+            $tail =~ s/<\/\?>//s; # Remove the closing tag
+        } else {
+            $tail =~ s/.*?<\/\?>//s; # Remove up to the closing tag
+        }
+        $template = $head . $tail;
+    }
+    return $template;
+}
+
+
 sub interpolate {
   return sub {
 
@@ -37,29 +110,32 @@ sub interpolate {
     my $template = shift;
 
     # Backward Compatibility with core Blosxom style interpolation
-    $template =~ s#(?<!<)(?<!<\?)(?<!<\?!)(\$\w+(?:::)?\w*)#<$1 />#gs; 
+    unless ($parsing_story)
+       {$template =~ s#(?<!<)(?<!<\?)(?<!<\?!)(\$\w+(?:::\w+)*)#<$1 />#gs; };
 
+    #
+    # Conditional inclusion
     # Defined
     # e.g. <?$var>display if defined</?>
-    $template =~ s#<\?(\$\w+(?:::)?\w*)>(.*?)<\/\?>#"defined $1 ? \$2 : undef"#gsee;
-
     # Undefined 
     # e.g. <?!$var>display if not defined</?>
-    $template =~ s#<\?!(\$\w+(?:::)?\w*)>(.*?)<\/\?>#"!defined $1 ? \$2 : undef"#gsee;
-
     # Tests 
     # eq (eq), ne (ne), lt (<), gt (>), like (=~), unlike (!~)
     # e.g. <?$var lt="123">display if $var less than 123</?>
-    $template =~ s#<\?(\$\w+(?:::)?\w*)\s+?(.+?)>(.*?)<\/\?>#"interpolate_fancy::_test(qq{$1}, q{$2}, q{$3})"#gsee;
+    $template = interpolate_fancy::_resolve_nested($template, 1);
 
-    # Unconditional, Recursive 
+    #
+    # Variable expansion (unconditional, recursive)
+    #
     # e.g. <$var />
-    while( $template =~ s/<\$([a-zA-Z?]\w+(?:::)?\w*)\s+?\/>/"defined \$$1 ? \$$1 : undef"/gsee ) { }
+    while( $template =~ s/<\$([a-zA-Z?]\w+(?:::\w+)*)\s+?\/>/"defined \$$1 ? \$$1 : undef"/gsee ) { }
 
+    #
     # Actions 
+    #
     # e.g. <@plugin.subroutine arg1="a" output="no" />
     # e.g. <@plugin.subroutine encoding="Latin1" output="yes">pass content</@> 
-    $template =~ s#<\@(\w+?)\.(\w+?)\s+?(.+?)?(?:>(.*?)<\/\@\1\.\2>|\s+?\/>)#&interpolate_fancy::_action($1,$2,$3,$4)#gse;
+    $template =~ s#<\@((?:\w|::)+?)\.(\w+?)\s+?(.+?)?(?:>(.*?)<\/\@\1\.\2>|\s+?\/>)#&interpolate_fancy::_action($1,$2,$3,$4)#gse;
 
     # Unmangle mangled interpolation tags in story $title and $body
     # (by now in the template itself)
@@ -73,30 +149,28 @@ sub interpolate {
 }
 
 sub _test {
-  my($variable, $attr, $content) = @_;
-
-  my $result;
-
+  my($variable, $attr) = @_;
   my $attributes = interpolate_fancy::_attributes($attr);
 
-  defined $attributes->{eq} and return $variable eq $attributes->{eq} ? $content : undef;
-  defined $attributes->{ne} and return $variable ne $attributes->{ne} ? $content : undef;
-  defined $attributes->{gt} and return $variable > $attributes->{gt} ? $content : undef;
-  defined $attributes->{lt} and return $variable < $attributes->{lt} ? $content : undef;
-  defined $attributes->{like} and return $variable  =~ /$attributes->{like}/ ? $content : undef;
-  defined $attributes->{unlike} and return $variable  !~ /$attributes->{unlike}/ ? $content : undef;
+  defined $attributes->{eq} and return $variable eq $attributes->{eq};
+  defined $attributes->{ne} and return $variable ne $attributes->{ne};
+  defined $attributes->{gt} and return $variable > $attributes->{gt};
+  defined $attributes->{lt} and return $variable < $attributes->{lt};
+  defined $attributes->{like} and return $variable  =~ /$attributes->{like}/;
+  defined $attributes->{unlike} and return $variable  !~ /$attributes->{unlike}/;
 
-  return undef;
+  return 0;
 }
 
 sub _action {
   my($plugin, $action, $attr, $content) = @_;
+  my $result;
 
-  $content =~ s#<\@(\w+?)\.(\w+?)\s+?(.+?)?(?:>(.*?)<\/\@\1\.\2>|\s+?\/>)#&interpolate_fancy::_action($1,$2,$3,$4)#gse;
+  $content =~ s#<\@((?:\w|::)+?)\.(\w+?)\s+?(.+?)?(?:>(.*?)<\/\@\1\.\2>|\s+?\/>)#&interpolate_fancy::_action($1,$2,$3,$4)#gse;
 
   my $attributes = interpolate_fancy::_attributes($attr);
   
-  $blosxom::plugins{$plugin} 
+  $blosxom::plugins{$plugin} > 0
     and $plugin->can($action) 
       and $result = $plugin->$action($attributes, $content);
 
@@ -114,6 +188,18 @@ sub _attributes {
   return $attributes;
 }
 
+sub story {
+       my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+ if ($recurse_into_story) {
+      $parsing_story = 1;
+      $$title_ref =&$blosxom::interpolate($$title_ref);
+      $$body_ref =&$blosxom::interpolate($$body_ref);
+      $parsing_story = 0;
+     }
+  return 1;
+}
+
 1;
 
 __END__
@@ -124,121 +210,110 @@ Blosxom Plug-in: interpolate_fancy
 
 =head1 SYNOPSIS
 
-Overrides Blosxom's far simpler to use, but more limited, default interpolate() 
-subroutine.
+Overrides Blosxom's far simpler to use, but more limited, default interpolate() subroutine.
+
+* Include bits of text and template variable values in templates, either conditionally or unconditionally;
 
-Include bits of text and template variable values in templates, either 
-conditionally or unconditionally:
+* Perform actions (i.e. call plug-in subroutines) at any point in your page, whether to act on current content and return results or not.
 
-Perform actions (i.e. call plug-in subroutines) at any point in your page, 
-whether to act on current content and return results or no.
+=head2 Inclusions of bits of text and variables
+This syntax will expand to the value of the referenced variable and can be used to include dynamic content in your pages.
 
-=head2 Includes
+=over 2
 
-  * Unconditionally and recursively
+=item Unconditionally and recursively
 
-    e.g. include a link to the story's path using various template variables.
+e.g. include a link to the story's path using various template variables.
 
-    <a href="<$url /><$path />"><$path /></a>
+<a href="<$url /><$path />"><$path /></a>
 
-    Limited by the $recurse_into_story configuration directive (see
-    the CONFIGURATION below).
+Limited by the $recurse_into_story configuration directive (see the CONFIGURATION below).
 
-  * Unconditionally and recursively (backward compatibility with Blosxom's standard interpolation)
+=item Unconditionally (backward compatibility with Blosxom's standard interpolation)
 
-    e.g. include a link to the story's path using various template variables.
+e.g. include a link to the story's path using various template variables (non-recursive, and only inside templates).
 
-    <a href="$url$path">$path</a>
+<a href="$url$path">$path</a>
 
-    Limited by the $recurse_into_story configuration directive (see
-    the CONFIGURATION below).
+=item If the template variable has a value (i.e. is defined)
 
-  * The template variable has a value (i.e. is defined)
+e.g. include a hyperlink to the story's path if it has a path (i.e. $path is defined).
 
-    e.g. include a hyperlink to the story's path if it has a path (i.e.
-    $path is defined).
+<?$path><a href="<$url /><$path />"><$path /></a></?>
 
-    <?$path><a href="<$url /><$path />"><$path /></a></?>
+=item If the template variable doesn't have a value (i.e. is NOT defined)
 
-  * The template variable doesn't have a value (i.e. is NOT defined)
+e.g. include a hyperlink to home if path is undefined.
 
-    e.g. include a hyperlink to home if path is undefined.
+<?!$path><a href="<$url />">home</a></?>
 
-    <?!$path><a href="<$url />">home</a></?>
+=item If the template variable is equal (=) to a particular value
 
-  * The template variable is equal (=) to a particular value
+e.g. include "1 writeback" (singular) if the value of writeback count is 1
 
-    e.g. include "1 writeback" (singular) if the value of writeback count is 1
+<$writeback::count /> <?$writeback::count eq="1">writeback</?>
 
-    <$writeback::count /> <?$writeback::count eq="1">writeback</?>
+=item If the template variable is not equal (!=) to a particular value
 
-  * The template variable is not equal (!=) to a particular value
+e.g. include "x writebacks" (plural) if the value of Writeback count (x) is not 1
 
-    e.g. include "x writebacks" (plural) if the value of writeback 
-         count (x) is not 1
+<$writeback::count /> <?$writeback::count ne="1">writebacks</?>
 
-    <$writeback::count /> <?$writeback::count ne="1">writebacks</?>
+=item If the template variable is less than (<) a particular value
 
-  * The template variable is less than (<) a particular value
+e.g. include "no writebacks" if the value of writeback count is < 1
 
-    e.g. include "no writebacks" if the value of writeback count is < 1
+<?$writeback::count lt="1">no writebacks</?>
 
-    <?$writeback::count lt="1">no writebacks</?>
+=item If the template variable is greater than (>) a particular value
 
-  * The template variable is greater than (>) a particular value
+e.g. include "oodles of writebacks" if the value of writeback count is > 50
 
-    e.g. include "oodles of writebacks" if the value of writeback count is > 50
+<?$writeback::count gt="50">oodles of writebacks</?>
 
-    <?$writeback::count gt="50">oodles of writebacks</?>
+=item If the template variable is like (=~) a particular regular expression
 
-  * The template variable is like (=~) a particular regular expression
+e.g. Greet a visitor properly, depending on their courtesy title
 
-    e.g. Greet a visitor properly, depending on their courtesy title
+Howdy, 
+<?$user::courtesy like="^(Mr\.?|Sir)$">Sir</?>
+<?$user::courtesy like="^(Mr?s\.?|Miss)$">M'am</?>
 
-    Howdy, 
-    <?$user::courtesy like="^(Mr\.?|Sir)$">Sir</?>
-    <?$user::courtesy like="^(Mr?s\.?|Miss)$">M'am</?>
+=item If the template variable is unlike (!~) a particular regular expression
 
-  * The template variable is unlike (!~) a particular regular expression
+e.g. The posting is neither a film nor a book
 
-    e.g. The posting is neither a film nor a book
+<?$path unlike="/(Film|Literature)">no review</?>
 
-    <?$path unlike="/(Film|Literature)">no review</?>
+=back
 
 =head2 Actions
 
-Perform an action (i.e. call a plug-in's subroutine) at any point in your page.
-Optionally pass arguments as key/value pairs stated as XML-style attributes.
+Performs an action (i.e. calls a plug-in's subroutine) at any point in your page. Optionally passes arguments as key/value pairs stated as XML-style attributes.
+
 For example: 
 
-  <@plugin.subroutine arg1="a" arg2="bee" />
+<@thePlugin.subroutine arg1="a" arg2="bee" />
 
-calls &plugin::subroutine( {'arg1'=>'a', 'arg2'=>'bee' } ).
+calls &Blosxom::Plugin::ThePlugin::subroutine( {'arg1'=>'a', 'arg2'=>'bee' } ).
 
-Specify that results should be sent to the browser using the output="yes" 
-attribute like so:
-  
-  <@plugin.subroutine arg1="a" arg2="bee" output="yes" />
+Specify that results should be sent to the browser using the output="yes" attribute like so:
+<@thePlugin.subroutine arg1="a" arg2="bee" output="yes" />
 
-Otherwise, subroutines will still have their effect, but the results will 
-be tossed out.
+Otherwise, subroutines will still have their effect, but the results will be tossed out.
 
-Content wrapped in the action call is sent as another argument to the 
-subroutine:
+Content wrapped in the action call is sent as another argument to the subroutine:
 
-  <@plugin.subroutine encoding="Latin1" output="yes">
+  <@thePlugin.subroutine encoding="Latin1" output="yes">
   pass this content
-  </@plugin.subroutine> 
+  </@thePlugin.subroutine> 
 
-This calls &plugin::subroutine( {'encoding'=>'Latin1', 'output'=>'yes' }, "pass this content" ).
+This calls &thePlugin::subroutine( {'encoding'=>'Latin1', 'output'=>'yes' }, "pass this content" ).
 
-Actions are recursive, meaning that you can embed an action inside another, 
-and so on and so on and so on.  Actions are unfolded from the inside out,
-with the most deeply embedded first, second deepest second, and so forth until
-the outermost action is performed last.
+Actions are recursive, meaning that you can embed an action inside another, and so on and so on and so on.  Actions are unfolded from the inside out, with the most deeply embedded first, second deepest second, and so forth until the outermost action is performed last.
 
-Recursion is limited by the $recurse_into_story configuration directive (see
-the CONFIGURATION below).
+Recursion is limited by the $recurse_into_story configuration directive (see the CONFIGURATION below).
 
 =head3 An Example
 
@@ -248,22 +323,22 @@ sample actions:
 
 --
 
-# For the sake of this example, assume these actions live in a "myplugin"
-# plugin
+# For the sake of this example, assume these actions live in a "myplugin" plugin
 
 # This action strips HTML from its content
+
 sub strip_html {
   my($self, $attributes, $content) = @_;
   $content =~ s!</?.+?>!!g;
   return $content;
 }
 
-# This action foreshortens its content to a length specified in the call to
-# action's length attribute
+# This action foreshortens its content to a length specified in the call to action's length attribute
+
 sub foreshorten {
   my($self, $attributes, $content) = @_;
   my $default_length = 144;
-  return substr($content, 0, $attributes{'length'}||$default_length);
+  return substr($content, 0, $attributes->{'length'}||$default_length);
 }
 
 --
@@ -271,30 +346,26 @@ sub foreshorten {
 Calling these individually in a Blosxom flavour template looks something like:
 
 The following bit of text is devoid of HTML:
+
 <@myplugin.strip_html output="Yes">
 Silly <a href="http://www.raelity.org/">me</a>, I plumb 
 <em>forgot</em> to remove the HTML.
 </@myplugin.strip_html>
 
 The following bit of text is only 20 characters in length:
-<@myplugin.foreshorten output="Yes">
-This text is far longer than 20 characters on the page, 
-but will only appear as "This text is far lon" in the
+
+<@myplugin.foreshorten length="20" output="Yes">This text is far longer than 20 characters on the page, but will only appear as "This text is far lon" in the
 resulting page.
 </@myplugin.foreshorten>
 
 And in combination, stripping the HTML _before_ foreshortening (notice
 the strip_html action is embedded inside the foreshorten action and
-thus is run first):
+thus is run first).
 
 The following bit of text is only 20 characters in length and devoid of HTML:
-<@myplugin.foreshorten output="Yes">
-<@myplugin.strip_html output="Yes">
-Silly <a href="http://www.raelity.org/">me</a>, I plumb 
+
+<@myplugin.foreshorten length="20" output="Yes"><@myplugin.strip_html output="Yes">Silly <a href="http://www.raelity.org/">me</a>, I plumb 
 <em>forgot</em> to remove the HTML.
-This text is far longer than 20 characters on the page, 
-but will only appear as "This text is far lon" in the
-resulting page.
 </@myplugin.strip_html>
 </@myplugin.foreshorten>
 
@@ -332,26 +403,39 @@ requiring no template rewriting on your part.
 If you've been using the interpolate_conditional plugin,
 the conditional bits won't be respected by default.  You should
 run your templates through the interpolate2fancy utility
-[http://blosxom.sourceforge.net/downloads/utilities/interpolate2fancy.py].
+[http://www.blosxom.com/downloads/utilities/interpolate2fancy.py].
 
 =head1 VERSION
 
-2003-09-09:12:10
+2007-09-27
+
+=head1 VERSION HISTORY
+
+2007-09-27 : enables more than one :: in variable names
+
+2007-09-13 : corrects the $recurse_into_story feature in XML flavours
+
+v20061114 : fixes from Matthijs Kooijman (including properly support for nested
+conditions)
 
 =head1 AUTHOR
 
 Rael Dornfest  <rael@oreilly.com>, http://www.raelity.org/
+Modified by Matthijs Kooijman <m.kooijman@student.utwente.nl>, http://katherina.student.utwente.nl/~matthijs/blog
+
+This plugin is now maintained by the Blosxom Sourceforge Team,
+<blosxom-devel@lists.sourceforge.net>.
 
 =head1 SEE ALSO
 
-Blosxom Home/Docs/Licensing: http://www.raelity.org/apps/blosxom/
+Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
 
-Blosxom Plugin Docs: http://www.raelity.org/apps/blosxom/plugin.shtml
+Blosxom Plugin Docs: http://blosxom.sourceforge.net/documentation/users/plugins.html
 
 =head1 BUGS
 
-Address bug reports and comments to the Blosxom mailing list 
-[http://www.yahoogroups.com/group/blosxom].
+None known; please send bug reports and feedback to the Blosxom
+development mailing list <blosxom-devel@lists.sourceforge.net>.
 
 =head1 LICENSE