Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package Text::PostTemplate;
- use strict;
- use warnings;
- use Carp;
- use Encode;
- use Scalar::Util qw(looks_like_number);
- our $VERSION = '0.006';
- my %XML_SPECIAL = (
- q(&) => q(&), q(<) => q(<), q(>) => q(>),
- q(") => q("), q(') => q('), q(\\) => q(\),
- );
- my $ALPHA = q(A-Za-z);
- my $DIGIT = q(0-9);
- my $ALNUM = q(A-Za-z0-9);
- my %EMPTY_ELEMENT = map { $_ => 1 } qw(
- meta img link br hr input area param col base
- );
- my %ATTRIBUTE_TYPE = (
- (map { $_ => 'uri' } qw(href src action)),
- (map { $_ => 'htmlall' } qw(value)),
- (map { $_ => 'bool' } qw(
- compact nowrap ismap declare noshade checked disabled readonly
- multiple selected noresize defer
- )),
- );
- my %OPERATOR = (
- # SPECIAL OPERATOR ($op & 0xc0) == 0x00
- '% LIT' => [0x00, sub{}],
- '% VAR' => [0x01, sub{}],
- 'index' => [0x02, \&_op_index],
- 'var' => [0x03, \&_op_var],
- 'def' => [0x04, \&_op_def],
- 'put' => [0x05, \&_op_put],
- 'if' => [0x06, \&_op_if],
- 'ifelse' => [0x07, \&_op_ifelse],
- 'for' => [0x08, \&_op_for],
- 'with' => [0x09, \&_op_forall],
- 'forall' => [0x09, \&_op_forall],
- 'br' => [0x0a, \&_op_br],
- 'sp' => [0x0b, \&_op_sp],
- '% ELEM' => [0x0c, \&_op_element],
- '% ATTR' => [0x0d, \&_op_attribute],
- # BINARY OPERATOR ($op & 0xc0) == 0x40
- 'exch' => [0x40, sub { ($_[1], $_[0]) }],
- 'add' => [0x41, sub { $_[0] + $_[1] }],
- 'sub' => [0x42, sub { $_[0] - $_[1] }],
- 'mul' => [0x43, sub { $_[0] * $_[1] }],
- 'div' => [0x44, sub { $_[0] / $_[1] }],
- 'mod' => [0x45, sub { $_[0] % $_[1] }],
- 'eq' => [0x46, sub { _compare(@_) == 0 ? 1 : 0 }],
- 'ne' => [0x47, sub { _compare(@_) != 0 ? 1 : 0 }],
- 'lt' => [0x48, sub { _compare(@_) < 0 ? 1 : 0 }],
- 'le' => [0x49, sub { _compare(@_) <= 0 ? 1 : 0 }],
- 'gt' => [0x4a, sub { _compare(@_) > 0 ? 1 : 0 }],
- 'ge' => [0x4b, sub { _compare(@_) >= 0 ? 1 : 0 }],
- 'get!' => [0x4c, sub { _fetch(@_, \&_escape_raw) }],
- 'raw_get!' => [0x4c, sub { _fetch(@_, \&_escape_raw) }],
- 'get' => [0x4d, sub { _fetch(@_, \&_escape_html) }],
- 'html_get' => [0x4d, sub { _fetch(@_, \&_escape_html) }],
- 'htmlall_get' => [0x4e, sub { _fetch(@_, \&_escape_htmlall) }],
- 'uri_get' => [0x4f, sub { _fetch(@_, \&_escape_uri) }],
- 'uriall_get' => [0x50, sub { _fetch(@_, \&_escape_uriall) }],
- # UNARY OPERATOR ($op & 0xc0) == 0x80
- 'not' => [0x80, sub { ! $_[0] ? 1 : 0 }],
- 'neg' => [0x81, sub { -$_[0] }],
- 'dup' => [0x82, sub { ($_[0], $_[0]) }],
- 'pop' => [0x83, sub { () }],
- 'length' => [0x84, \&_op_length],
- 'int' => [0x85, sub { int $_[0] }],
- 'even' => [0x86, sub { $_[0] % 2 == 0 ? 1 : 0 }],
- 'odd' => [0x87, sub { $_[0] % 2 != 0 ? 1 : 0 }],
- );
- my @INSTRUCTION;
- for my $code (values %OPERATOR) {
- $INSTRUCTION[$code->[0]] = $code->[1];
- }
- sub compile {
- my($class, $source) = @_;
- my @block = ([]);
- my $lit = $OPERATOR{'% LIT'}[0];
- my $var = $OPERATOR{'% VAR'}[0];
- while ($source =~ m{\G(.*?)\[\%\s*(.*?)\s*\%\]\n?}gcmsx) {
- my($data, $word_list) = ($1, $2);
- if ($data ne q()) {
- push @{$block[-1]}, $lit, $data;
- }
- for my $word (split /\s+/msx, $word_list) {
- if ($word eq '{') {
- push @block, [];
- next;
- }
- if ($word eq '}') {
- my $a = pop @block;
- push @{$block[-1]}, $a;
- next;
- }
- if (looks_like_number($word)) {
- push @{$block[-1]}, $lit, $word;
- next;
- }
- if (length $word > 1) {
- if (q(/) eq (substr $word, 0, 1)) {
- push @{$block[-1]}, $lit, (substr $word, 1);
- next;
- }
- if ($word =~ m{\A%([$ALPHA][$ALNUM:_-]*)(/|!?=)?\z}mosx) {
- my($tag, $mode) = ($1, $2 || q());
- if ($mode ne q(/) && $EMPTY_ELEMENT{$tag}) {
- $mode = q(/);
- }
- push @{$block[-1]}, $OPERATOR{'% ELEM'}[0], $tag, $mode;
- next;
- }
- if (q(=) eq (substr $word, -1)
- && $word =~ m{\A([$ALNUM:_-]+)=\z}mosx
- ) {
- push @{$block[-1]}, $OPERATOR{'% ATTR'}[0], $1;
- next;
- }
- }
- if (exists $OPERATOR{$word}) {
- push @{$block[-1]}, $OPERATOR{$word}[0];
- }
- else {
- push @{$block[-1]}, $var, $word;
- }
- }
- }
- if (@block != 1) {
- croak 'Template Syntax Error';
- }
- if (pos $source) {
- $source = substr $source, pos $source;
- }
- if ($source ne q()) {
- push @{$block[-1]}, $lit, $source;
- }
- return $block[0];
- }
- sub apply {
- my($class, $template, $param) = @_;
- my $result = _execute([q()], $template, $param);
- return join q(), @{$result};
- }
- sub _execute {
- my($data, $block, $param) = @_;
- my $pc = 0;
- while ($pc <= $#{$block}) {
- my $op = $block->[$pc++];
- next if ref $op; # BLOCK
- my $code = $INSTRUCTION[$op]
- or croak sprintf "Template Error : undefined opcode 0x%02x", $op;
- if ($op == 0x00) { # LIT
- push @{$data}, $block->[$pc++];
- }
- elsif ($op == 0x01) { # VAR
- push @{$data}, _fetch($param, $block->[$pc++], \&_escape_html);
- }
- elsif (($op & 0xc0) == 0x40) { # BINARY OPERATORS
- my($a, $b) = splice @{$data}, -2;
- push @{$data}, $code->($a, $b);
- }
- elsif (($op & 0xc0) == 0x80) { # UNARY OPERATORS
- my $a = pop @{$data};
- push @{$data}, $code->($a);
- }
- else { # SPECIAL OPERATORS
- $code->($data, $block, $param, \$pc);
- }
- }
- return $data;
- }
- sub _opland {
- my($block, $param, $refpc) = @_;
- my $opland = $block->[${$refpc}++];
- if (ref $opland) {
- my $a = _execute([], $opland, $param);
- return join q(), @{$a};
- }
- elsif ($opland == 0x00) {
- return $block->[${$refpc}++];
- }
- elsif ($opland == 0x01) {
- return $param->{$block->[${$refpc}++]};
- }
- return q();
- }
- sub _op_index {
- my($data, $block, $param, $refpc) = @_;
- my $i = pop @{$data};
- my $r = $data->[-1 - $i];
- push @{$data}, $r;
- return;
- }
- sub _op_var {
- my($data, $block, $param, $refpc) = @_;
- push @{$data}, $param;
- return;
- }
- sub _op_def {
- my($data, $block, $param, $refpc) = @_;
- my($key, $value) = splice @{$data}, -2;
- _store($param, $key, $value);
- return;
- }
- sub _op_put {
- my($data, $block, $param, $refpc) = @_;
- my($value, $obj, $key) = splice @{$data}, -3;
- _store($obj, $key, $value);
- return;
- }
- sub _op_if {
- my($data, $block, $param, $refpc) = @_;
- my $cond = pop @{$data};
- if ($cond) {
- _execute($data, $block->[${$refpc} - 2], $param);
- }
- return;
- }
- sub _op_ifelse {
- my($data, $block, $param, $refpc) = @_;
- my $cond = pop @{$data};
- if ($cond) {
- _execute($data, $block->[${$refpc} - 3], $param);
- }
- else {
- _execute($data, $block->[${$refpc} - 2], $param);
- }
- return;
- }
- sub _op_for {
- my($data, $block, $param, $refpc) = @_;
- my($i, $d, $limit) = splice @{$data}, -3;
- while (($d > 0 && $i <= $limit) || ($d < 0 && $i >= $limit)) {
- push @{$data}, $i;
- _execute($data, $block->[${$refpc} - 2], $param);
- $i += $d;
- }
- return;
- }
- sub _op_forall {
- my($data, $block, $param, $refpc) = @_;
- my $list = pop @{$data};
- if (ref $list ne 'ARRAY') {
- $list = [$list];
- }
- for my $i (0 .. $#{$list}) {
- my $item = $list->[$i];
- _execute($data, $block->[${$refpc} - 2], {
- %{$param},
- 'i' => $i,
- ref $item eq 'HASH' ? %{$item} : ('item' => $item),
- });
- }
- return;
- }
- sub _op_br {
- my($data, $block, $param, $refpc) = @_;
- push @{$data}, "\n";
- return;
- }
- sub _op_sp {
- my($data, $block, $param, $refpc) = @_;
- push @{$data}, q( );
- return;
- }
- sub _op_element {
- my($data, $block, $param, $refpc) = @_;
- my $attr = [];
- if (${$refpc} >= 2 && ref $block->[${$refpc} - 2]) {
- $attr = _execute([], $block->[${$refpc} - 2], $param);
- }
- my $attrstr = join q( ), q(), @{$attr};
- my $tag = $block->[${$refpc}++];
- my $mode = $block->[${$refpc}++];
- if ($mode eq q(/)) {
- my $br = $tag eq 'br' || $tag eq 'hr' ? "\n" : q();
- push @{$data}, "<$tag$attrstr />$br";
- return;
- }
- push @{$data}, qq(<$tag$attrstr>);
- if (ref $block->[${$refpc}] && ! $mode) {
- _execute($data, $block->[${$refpc}++], $param);
- }
- else {
- $mode ||= q(=);
- my $value = _opland($block, $param, $refpc);
- push @{$data},
- $mode eq q(!=) ? _escape_raw($value)
- : $tag eq 'textarea' ? _escape_htmlall($value)
- : _escape_html($value);
- }
- push @{$data}, qq(</$tag>);
- return;
- }
- sub _op_attribute {
- my($data, $block, $param, $refpc) = @_;
- my $attribute = $block->[${$refpc}++];
- my $value = _opland($block, $param, $refpc);
- my $type = $ATTRIBUTE_TYPE{$attribute} || 'html';
- if ($type eq 'uri') {
- push @{$data}, qq($attribute=") . _escape_uri($value) . q(");
- }
- elsif ($type eq 'htmlall') {
- push @{$data}, qq($attribute=") . _escape_htmlall($value) . q(");
- }
- elsif ($type eq 'bool') {
- if ($value) {
- push @{$data}, qq($attribute="$attribute");
- }
- }
- else {
- push @{$data}, qq($attribute=") . _escape_html($value) . q(");
- }
- return;
- }
- sub _op_length {
- my($a) = @_;
- return ref $a eq 'ARRAY' ? $#{$a} + 1 : length $a;
- }
- sub _compare {
- my($a, $b) = @_;
- if (looks_like_number($a) && looks_like_number($b)) {
- return $a <=> $b;
- }
- else {
- return $a cmp $b;
- }
- }
- sub _fetch {
- my($obj, $key, $filter) = @_;
- $filter ||= \&_escape_html;
- my $value = ref $obj eq 'HASH' ? $obj->{$key}
- : ref $obj eq 'ARRAY' ? $obj->[$key]
- : eval{ $obj->can($key) } ? $obj->$key
- : q();
- return q() if ! defined $value;
- return $value if ref $value || looks_like_number($value);
- return $filter->($value);
- }
- sub _store {
- my($obj, $key, $value) = @_;
- if (ref $obj eq 'HASH') {
- $obj->{$key} = $value;
- }
- elsif (ref $obj eq 'ARRAY') {
- $obj->[$key] = $value;
- }
- elsif (eval{ $obj->can($key) }) {
- $obj->$key($value);
- }
- return;
- }
- sub _escape_htmlall {
- my($t) = @_;
- $t = defined $t ? $t : q();
- $t =~ s{([&<>"'\\])}{ $XML_SPECIAL{$1} }egmosx;
- return $t;
- }
- sub _escape_html {
- my($t) = @_;
- $t =~ s{
- (?:([<>"'\\])
- |\&(?:([_$ALPHA][_$ALNUM]*|\#(?:[$DIGIT]{1,5}|x[[:xdigit:]]{2,4}));)?
- )
- }{
- $1 ? $XML_SPECIAL{$1} : $2 ? qq(\&$2;) : q(&)
- }egmosx;
- return $t;
- }
- sub _escape_uriall {
- my($t) = @_;
- if (utf8::is_utf8($t)) {
- $t = Encode::encode('UTF-8', $t);
- }
- $t =~ s{([^$ALNUM\-_~/.,;:])}{ sprintf '%%%02X', ord $1 }egmosx;
- return $t;
- }
- sub _escape_uri {
- my($t) = @_;
- if (utf8::is_utf8($t)) {
- $t = Encode::encode('UTF-8', $t);
- }
- $t =~ s{
- (%([[:xdigit:]]{2})?)|(&(?:amp;)?)|([^$ALNUM\-_~*+=/.!,;:\@?\#])
- }{
- $2 ? $1 : $1 ? q(%25) : $3 ? q(&) : sprintf '%%%02X', ord $4
- }egmosx;
- return $t;
- }
- sub _escape_raw { return $_[0] }
- 1;
- __END__
- =pod
- =head1 NAME
- Text::PostTemplate - One of template processors
- =head1 VERSION
- 0.006
- =head1 SYNOPSYS
- use Encode;
- use Text::PostTemplate;
- my $template = Text::PostTemplate->compile(<<'EOS');
- <!DOCTYPE html>
- <html>
- <head><title>Example</title></head>
- <body>
- [%
- { id= /preview } %div!= entry.body br
- { action= entry.postlink method= /post } %form { br
- { name= /token type= /hidden value= session.token } %input/ br
- { name= /body } %textarea= entry.body br
- %]<input type="submit" value=" POST " />[% br
- } br
- %]
- </body>
- </html>
- EOS
- my $output = Text::PostTemplate->apply($template, {
- 'entry.postlink' => 'post.cgi',
- 'session.token' => 'ateEdC39ag2kng9',
- 'entry.body' => '<p>Hello, world!</p>',
- });
- print encode('UTF-8', $output);
- =head1 DESCRIPTION
- =head1 METHODS
- =over
- =item1 C<compile>
- =item1 C<apply>
- =back
- =head1 DEPENDENCIES
- L<Scalar::Util>
- L<Encode>
- =head1 AUTHOR
- MIZUTANI Tociyuki C<< <tociyuki@gmail.com> >>
- =head1 LICENSE AND COPYRIGHT
- Copyright (c) 2012, MIZUTANI Tociyuki C<< <tociyuki@gmail.com> >>.
- All rights reserved.
- This module is free software; you can redistribute it and/or
- modify it under the same terms as Perl itself.
- =cut
Add Comment
Please, Sign In to add comment