plTenjin FAQ

last update: $Date: 2008-02-04 22:21:53 +0900 (Mon, 04 Feb 2008) $

Release: 0.0.2

Table of contents:

Basic

I got an SyntaxError exception.

Command-line option '-z' checks syntax of template file. You should check template by it.

Also command-line option '-w' is useful. This option enables 'use strict;' of Perl.

File 'ex1.plhtml':
<ul>
<?pl for $i (1..10) { ?>
  <li>[=$i=]</li>
<?pl } ?>
</ul>
Result:
$ pltenjin -z ex1.plhtml
*** data/faq/ex1.plhtml - ok
$ pltenjin -wz ex1.plhtml
*** data/faq/ex1.plhtml - NG
Global symbol "$i" requires explicit package name at ex1.plhtml line 2.
Global symbol "$i" requires explicit package name at ex1.plhtml line 3.
ex1.plhtml had compilation errors.

'[== $_content =]' includes extra newline at end. Can I delete it?

Yes. You can use '<?pl echo($_content); ?>' or '<?pl push(@_buf, $_context); ?> or '[== $_context ==]' instead of '[== $_context =]'.

File 'ex2-layout.plhtml':
<!-- -->
[== $_content =]
<!-- -->

<!-- -->
[== $_content ==]
<!-- -->

<!-- -->
<?pl push(@_buf, $_content); ?>
<!-- -->
File 'ex2-content.plhtml':
foo
bar
baz
Result:
$ pltenjin --layout=ex2-layout.plhtml ex2-content.plhtml
<!-- -->
foo
bar
baz

<!-- -->

<!-- -->
foo
bar
baz
<!-- -->

<!-- -->
foo
bar
baz
<!-- -->

Can I change 'escape()' function name?

Yes. You can change them by setting 'escapefunc' option for Tenjin::Template::new() or Tenjin::Engine::new().

File 'ex3.pl':
use Tenjin;
use strict;
my $engine = new Tenjin::Engine({escapefunc=>'HTML::Entities::encode_entities'});
my $template = $engine->get_template('ex3.plhtml');
print $template->{script};
File 'ex3.plhtml':
Hello [=$name=]!
Result:
$ perl ex3.pl
my @_buf = (); push(@_buf, q`Hello `, HTML::Entities::encode_entities($name), q`!
`, ); join('', @_buf);

Command-line option '--escapefunc=name' is equivarent to the above.

Result:
$ pltenjin -s --escapefunc=HTML::Entities::encode_entities ex3.plhtml
my @_buf = (); push(@_buf, q`Hello `, HTML::Entities::encode_entities($name), q`!
`, ); join('', @_buf);

Can I change '@_buf' variable name?

No. Variable name '@_buf' should not and will never be changed.

Is it able to specify default value if a variable is not set?

Yes, by $_context->{varname}. It is able to check whether value is set or not by inspecting special variable '$_context' which represents context values.

File 'ex4.plhtml':
<?pl my $user = defined($_context->{'user'}) ? $_context->{'user'} : 'Guest';  ?>
Hello [=$user=]!
Result:
$ pltenjin -c 'user=>"Tenjin"' ex4.plhtml
Hello Tenjin!
$ pltenjin ex4.plhtml
Hello Guest!

Template

Is it possible to specify variables passed to template?

Yes. You can specify template arguments by '<?pl #@ARGS arg1, arg2, arg3 ?>'.

File 'ex5-layout.plhtml'
<?xml version="1.0 ?>
<?pl #@ARGS x, y ?>
<p>
  x = [=$x=]
  y = [=$y=]
  z = [=$z=]
</p>

Template arguments line is converted into local variable assignment statements.

Source code
$ pltenjin -s ex5.plhtml
my @_buf = (); push(@_buf, q`<?xml version="1.0 ?>
`, ); my $x = $_context->{x}; my $y = $_context->{y}; 
push(@_buf, q`<p>
  x = `, escape($x), q`
  y = `, escape($y), q`
  z = `, escape($z), q`
</p>
`, ); join('', @_buf);

Undeclared arguments are not available even when they are passed via context object.

Result:
$ pltenjin -wc 'x=>10, y=>20' ex5.plhtml
*** Error: ex5.plhtml
Global symbol "$z" requires explicit package name at (eval 4) line 6.

Is it able to change embedded expression pattern?

Yes, you can create subclass of Template class and override embedded expression pattern.

ex6-expr-pattern.plhtml:
<p>HTML escaped: [|$value|]</p>
<p>not escaped:  [:$value:]</p>
ex6-expr-pattern.pl:
use strict;
use Tenjin;

##
## template class
##
package MyTemplate;

@MyTemplate::ISA = ('Tenjin::Template');

## '[|expr|]' escapes HTML and '[:expr:]' doesn't
my $EXPR_PATTERN = qr/\[(?:\|(.*?)\||\:(.*?)\:)(-)?\]/s;

### return embedded expression pattern
sub expr_pattern {
    my $this = shift;
    return $EXPR_PATTERN;
}

## return expression string and flag whether escape or not
sub get_expr_and_escapeflag {
    my $this = shift;
    my ($m1, $m2, $m3) = @_;
    my $expr = $m1 || $m2;
    my $escapeflag = $m1 ? 1 : 0;
    my $remove_newline = $m3 ? 1 : 0;
    return $expr, $escapeflag, $remove_newline
}

##
## test program
##
package main;
if (__FILE__ == $0) {
    my $context = { value => 'AAA&BBB' };
    my $engine = new Tenjin::Engine({templateclass=>'MyTemplate'});
    my $output = $engine->render('ex6-expr-pattern.plhtml', $context);
    print $output;
}
Result:
$ perl ex6-expr-pattern.pl
<p>HTML escaped: AAA&amp;BBB</p>
<p>not escaped:  AAA&BBB</p>

Does plTenjin support M17N?

No, but it is easy to support M17N. The point is:

  • Change cache filename according to language. For example, create cache file 'file.plhtml.en.cache', 'file.plhtml.fr.cache', 'file.plhtml.it.cache', and so on from template file 'file.plhtml'.
  • Create Engine object for each language.
  • (optinal) Use preprocessing for performance reason.

The following is an example to generate M17N pages from a template file.

ex7-m18n.plhtml:
<div>
<?PL ## '_()' represents translator method ?>
 <p>[*=_('Hello')=*] [=$username=]!</p>
</div>
ex7-m18n.pl:
use strict;
use Tenjin;


##
## message catalog to translate message
##
my $MESSAGE_CATALOG = {
  'en' => { 'Hello'   => 'Hello',
            'Good bye'=> 'Good bye', },
  'fr' => { 'Hello'   => 'Bonjour',
            'Good bye'=> 'Au revoir', },
};

sub _ {
    my ($message_key, $lang) = @_;
    $lang = $::LANG || 'en' unless $lang;
    my $dict = $MESSAGE_CATALOG->{$lang};
    return $message_key unless $dict;
    return $dict->{$message_key} || $message_key;
}


##
## engine class which supports M17N
##
package M17NEngine;

@M17NEngine::ISA = ('Tenjin::Engine');

## get 'lang' option
sub new {
    my $class = shift;
    my $this = $class->SUPER::new(@_);
    my $opts = $_[0] || {};
    $this->{lang} = $opts->{lang} || 'en';
    return $this;
}


## change cache flename to 'file.html.lang.cache'
sub cachename {
    my $this = shift;
    my ($filename) = @_;
    return "$filename.$this->{lang}.cache"
}

## set/clear $::LANG before/after rendering
sub render {
    my $this = shift;
    $::LANG = $this->{lang};
    my $output = $this->SUPER::render(@_);
    $::LANG = undef;
    return $output;
}


##
## test program
##
package main;
if ($0 == __FILE__) {

    my $template_name = 'ex7-m18n.plhtml';
    my $context = { username=>'World' };

    ## engine for english
    my $engine = new M17NEngine({ preprocess=>1 });
    my $output = $engine->render($template_name, $context);
    print "--- lang: $engine->{lang} ---\n";
    print $output, "\n";

    ## engine for english
    my $engine = new M17NEngine({ preprocess=>1, lang=>'fr' });
    my $output = $engine->render($template_name, $context);
    print "--- lang: $engine->{lang} ---\n";
    print $output;

}
Result:
$ perl ex7-m18n.pl
--- lang: en ---
<div>
 <p>Hello World!</p>
</div>

--- lang: fr ---
<div>
 <p>Bonjour World!</p>
</div>

plTenjin doesn't provide M17N feature directly because requirements for M17N are different for each applications or frameworks. Some applications or frameworks adapt GetText library and others use its original M17N library. What plTenjin should do is not to provide M17N feature but to show an example to support M17N.

Layout Template

Can I change layout template name in a template file?

Yes. If you set $_context->{_layout}, its value is regarded as layout template name.

  • You can specify template file name (ex. 'user_list.plhtml') or template short name (ex. :list).
  • If you set 1 to '$_context->{_layout}', default layout template name is used instead.
  • It is able to N-th level nested template.

See the next section for details.

Can I nest layout templates for any depth?

Yes. If you set $_context->{_layout}, you can nest layout templates in any depth.

The following example shows that:

  • 'ex7-content.plhtml' uses 'ex7-mylayout.plhtml' as layout template.
  • 'ex7-mylayout.plhtml' uses 'ex7-baselayout.plhtml' as layout template.
File 'ex8-content.plhtml':
<?pl $_context->{'title'} = 'Changing Layout Template Test'; ?>
<?pl ## specify layout template name ?>
<?pl $_context->{'_layout'} = 'ex8-mylayout.plhtml'; ?>
foo
bar
baz
File 'ex8-mylayout.plhtml':
<?pl ## use default layout template name ?>
<?pl $_context->{'_layout'} = 1; ?>
<div id="content">
[== $_content ==]
</div>
File 'ex8-baselayout.plhtml':
<html>
  <body>
<?pl my $title = $_context->{'title'}; ?>
<?pl if ($title) { ?>
    <h1>[=$title=]</h1>
<?pl } ?>
[== $_content ==]
  </body>
</html>
Result:
$ pltenjin -w --layout=ex8-baselayout.plhtml ex8-content.plhtml
<html>
  <body>
    <h1>Changing Layout Template Test</h1>
<div id="content">
foo
bar
baz
</div>
  </body>
</html>

Can I disable default layout template for a certain template?

Yes. If you set 0 to $_context->{_layout}, default layout template will not be applied.

Is Django-like "Template Inheritance" supported?

No, but you can emulate it partially by combination of template capturing and '$_context->{_layout}'.

File 'ex9-baselayout.plhtml':
<html>
 <body>

<?pl ## if '$_context->{header_part}' is defined then print it, ?>
<?pl ## else print default header part. ?>
  <div id="header">
<?pl start_placeholder('header_part'); ?>
   <img src="img/logo.png" alt="logo" ?>
<?pl stop_placeholder(); ?>
  </div>

<?pl ## main content part ?>
  <div id="content">
[== $content_part ==]
  </div>

<?pl ## if '$_context->{footer_part} is defined then print it, ?>
<?pl ## else print default footer part. ?>
  <div id="footer">
<?pl start_placeholder('footer_part'); ?>
   <hr />
   <em>webmaster@example.com</em>
<?pl stop_placeholder(); ?>
  </div>
  
 </body>
</html>
File 'ex9-customlayout.plhtml':
<?pl ## '$_context->{_layout}' is equivarent to '{% extends "foobar.html" %}' ?>
<?pl ## in Django template engine. ?>
<?pl $_context->{_layout} = 'ex9-baselayout.plhtml'; ?>

<?pl ## you can override header or footer by capturing. ?>
<?pl start_capture('footer_part'); ?>
<address style="text-align:right">
  copyright&copy; 2007 kuwata-lab all rights reserved<br />
  <a href="webmaster&#64;kuwata-lab.com">webmaster&#64;kuwata-lab.com</a>
</address>
<?pl stop_capture(); ?>
File 'ex9-content.plhtml':
<?pl ## '$_context->{_layout}' is equivarent to '{% extends "foobar.html" %}' ?>
<?pl ## in Django template engine. ?>
<?pl $_context->{_layout} = 'ex9-customlayout.plhtml'; ?>

<?pl ## main content part ?>
<?pl start_capture('content_part'); ?>
<ul>
<?pl for my $item (@$items) { ?>
  <li>[=$item=]</li>
<?pl } ?>
</ul>
<?pl stop_capture(); ?>

'start_placeholder()' and 'stop_placeholder()' are pre-defined helper macros. For example,

<?pl start_placeholder('header_part'); ?>
   <img src="img/logo.png" alt="logo" ?>
<?pl stop_placeholder(); ?>

is equivarent to the following.

<?pl if (defined($_context->{'header-part'})) { ?>
<?pl     push(@_buf, $_context->{'header_part'}); ?>
<?pl } else { ?>
   <img src="img/logo.png" alt="logo" ?>
<?pl } ?>

The following is the result. It shows that footer part in baselayout is overrided by other templates.

Result:
$ pltenjin -c "items=>['AAA', 'BBB', 'CCC']" ex9-content.plhtml
<html>
 <body>

  <div id="header">
   <img src="img/logo.png" alt="logo" ?>
  </div>

  <div id="content">
<ul>
  <li>AAA</li>
  <li>BBB</li>
  <li>CCC</li>
</ul>
  </div>

  <div id="footer">
<address style="text-align:right">
  copyright&copy; 2007 kuwata-lab all rights reserved<br />
  <a href="webmaster&#64;kuwata-lab.com">webmaster&#64;kuwata-lab.com</a>
</address>
  </div>
  
 </body>
</html>

Performance

How fast is plTenjin compared with other solutions?

plTenjin contains benchmark script. This shows that plTenjin works much faster than other solutions.

MacOS X 10.4 Tiger, Intel CoreDuo 1.83GHz, Memory 2GB
$ cd pltenjin-X.X.X/benchmark
$ perl -v | grep built
This is perl, v5.8.8 built for darwin-2level
$ perl -I ../lib bench.pl -n 10000 
*** n = 10000
                         user         sys       total        real
tenjin                  9.8500      0.5700     10.4200     11.0000
tenjin-nocache         16.2500      0.4300     16.6800     17.0000
tenjin-reuse            5.7100      0.0100      5.7200      6.0000
tt                    102.6100      0.9700    103.5800    104.0000
tt-reuse               26.2500      0.0500     26.3000     26.0000
htmltmpl               46.2300      0.4700     46.7000     47.0000
htmltmpl-reuse         30.1700      0.0400     30.2100     31.0000

In addition, module size of plTenjin is small, and it is very light-weight to import it. This is important for CGI program. Other solutions may be very heavy to import the module and suitable only for apache module or FastCGI.

Why plTenjin is so fast?

Because it doesn't use template engine original language.

Other template engines, such as Template-Toolkit(perl), Django(python), or Smarty(php), has their original languages. This is not good idea for script language because:

  • They are slow.
  • Implementation will be complex.
  • Learning cost is high.

In addition, plTenjin is faster than Jakarta Velocity which is a very popular template engine in Java. (It means that dynamic Java is slower than script languages.)

Template engine should use their host language directly unless there are some kind of reasons.

Is there any way to get more speed?

'Tenjin::Template::defun(funcname, args, ...)' generates definition of subroutine. This may make plTenjin faster.

File 'ex10.pl':
use Tenjin;
my $template = new Tenjin::Template('ex10.plhtml');
my $defun = $template->defun('render_ex10', qw(title items));
print $defun;
File 'ex10.plhtml':
<html>
 <body>
  <h1>[=$title=]</h1>
  <ul>
  <?pl for my $item (@$items) { ?>
   <li>[=$item=]</li>
  </ul>
  <?pl } ?>
 </body>
</html>
Result:
$ perl ex10.pl
sub render_ex10 { my ($_context) = @_; my $title = $_context->{'title'}; my $items = $_context->{'items'}; my @_buf = (); push(@_buf, q`<html>
 <body>
  <h1>`, escape($title), q`</h1>
  <ul>
`, );   for my $item (@$items) {
push(@_buf, q`   <li>`, escape($item), q`</li>
  </ul>
`, );   }
push(@_buf, q` </body>
</html>
`, ); join('', @_buf);
}