# Copyright 2001-2003 Six Apart. This code cannot be redistributed without
# permission from www.movabletype.org.
#
# $Id: Builder.pm,v 1.23 2003/02/12 00:15:03 btrott Exp $
package MT::Builder;
use strict;
use MT::ErrorHandler;
@MT::Builder::ISA = qw( MT::ErrorHandler );
sub new { bless { }, $_[0] }
sub compile {
my $build = shift;
my($ctx, $text) = @_;
return [ ] unless $text;
my $state = local $build->{__state} = { tokens => [ ] };
$state->{text} = \$text;
my $pos = 0;
my $len = length $text;
while ($text =~ m!(<\$?MT(.+?)[\$/]?>)!gs) {
my($whole_tag, $tag) = ($1, $2);
($tag, my($args)) = split /\s+/, $tag, 2;
my $sec_start = pos $text;
my $tag_start = $sec_start - length $whole_tag;
_text_block($state, $pos, $tag_start) if $pos < $tag_start;
$args ||= '';
my %args;
while ($args =~ /(\w+)\s*=\s*(["'])(.*?)\2/gs) {
$args{$1} = $3;
}
my($h, $is_container) = $ctx->handler_for($tag);
my $rec = [ $tag, \%args ];
if ($is_container) {
if ($whole_tag !~ m|/>|) {
my ($sec_end, $tag_end) = _consume_up_to(\$text, $sec_start, $tag);
if ($sec_end) {
my $sec = substr $text, $sec_start, $sec_end - $sec_start;
$sec =~ s!^\n!!;
$rec->[2] = $build->compile($ctx, $sec) or return;
$rec->[3] = $sec;
} else {
return $build->error(" with no ");
}
$pos = $tag_end + 1;
(pos $text) = $tag_end;
} else {
$rec->[3] = '';
$pos = pos $text;
}
} else {
$pos = pos $text;
}
push @{ $state->{tokens} }, $rec;
$pos = pos $text;
}
_text_block($state, $pos, $len) if $pos < $len;
$state->{tokens};
}
sub _consume_up_to {
my($text, $start, $stoptag) = @_;
my $pos;
(pos $$text) = $start;
while ($$text =~ m!(<([\$/]?)MT($stoptag[^>]*?)[\$/]?>)!g) {
my($whole_tag, $prefix, $tag) = ($1, $2, $3);
($tag, my($args)) = split /\s+/, $tag, 2;
next if $tag ne $stoptag;
my $end = pos $$text;
if ($prefix && ($prefix eq '/')) {
return ($end - length($whole_tag), $end);
} elsif ($whole_tag !~ m|/>|) {
my ($sec_end, $end_tag) = _consume_up_to($text, $end, $tag);
last if !$sec_end;
(pos $$text) = $end_tag;
}
}
return (0, 0);
}
sub _text_block {
my $text = substr ${ $_[0]->{text} }, $_[1], $_[2] - $_[1];
push @{ $_[0]->{tokens} }, [ 'TEXT', $text ] if $text;
}
sub build {
my $build = shift;
my($ctx, $tokens, $cond) = @_;
$cond ||= { };
$ctx->stash('builder', $build);
my $res = '';
my $ph = $ctx->post_process_handler;
for my $t (@$tokens) {
if ($t->[0] eq 'TEXT') {
$res .= $t->[1];
} else {
my($tokens, $uncompiled);
if (exists $cond->{ $t->[0] } && !$cond->{ $t->[0] }) {
for my $tok (@{ $t->[2] }) {
if ($tok->[0] eq 'Else') {
$tokens = $tok->[2];
$uncompiled = $tok->[3];
last;
}
}
next unless $tokens;
} else {
if ($t->[2] && ref($t->[2]) eq 'ARRAY') {
$tokens = [ grep $_->[0] ne 'Else', @{ $t->[2] } ];
}
$uncompiled = $t->[3];
}
my($h) = $ctx->handler_for($t->[0]);
if ($h) {
$ctx->stash('tag', $t->[0]);
$ctx->stash('tokens', $tokens);
$ctx->stash('uncompiled', $uncompiled);
local $t->[1] = $t->[1];
my $out = $h->($ctx, $t->[1], $cond);
return $build->error("Error in [0]> tag: " .
$ctx->errstr) unless defined $out;
if ($ph) {
$out = $ph->($ctx, $t->[1], $out);
}
$res .= $out;
}
}
}
$res;
}
1;
__END__
=head1 NAME
MT::Builder - Parser and interpreter for MT templates
=head1 SYNOPSIS
use MT::Builder;
use MT::Template::Context;
my $build = MT::Builder->new;
my $ctx = MT::Template::Context->new;
my $tokens = $build->compile($ctx, '<$MTVersion$>')
or die $build->errstr;
defined(my $out = $build->build($ctx, $tokens))
or die $build->errstr;
=head1 DESCRIPTION
I provides the parser and interpreter for taking a template
body and turning it into a generated output page. An I object
knows how to parse a string of text into tokens, then take those tokens and
build a scalar string representing the output of the page. It does not,
however, know anything about the types of tags that it encounters; it hands
off this work to the I object, which can look up a
tag and determine whether it's valid, whether it's a container or substitution
tag, etc.
All I knows is the basic structure of a Movable Type tag, and
how to break up a string into pieces: plain text pieces interspersed with
tag callouts. It then knows how to take a list of these tokens/pieces and
build a completed page, using the same I object to
actually fill in the values for the Movable Type tags.
=head1 USAGE
=head2 MT::Builder->new
Constructs and returns a new parser/interpreter object.
=head2 $build->compile($ctx, $string)
Given an I object I<$ctx>, breaks up the scalar string
I<$string> into tokens and returns the list of tokens as a reference to an
array. Returns C on compilation failure.
=head2 $build->build($ctx, \@tokens [, \%cond ])
Given an I object I<$ctx>, turns a list of tokens
I<\@tokens> and generates an output page. Returns the output page on success,
C on failure. Note that the empty string (C<''>) and the number zero
(C<0>) are both valid return values for this method, so you should check
specifically for an undefined value when checking for errors.
The optional argument I<\%cond> specifies a list of conditions under which
the tokens will be interpreted. If provided, I<\%cond> should be a reference
to a hash, where the keys are MT tag names (without the leading C), and
the values are boolean flags specifying whether to include the tag; a true
value means that the tag should be included in the final output, a false value
that it should not. This is useful when a template includes conditional
container tags (eg CMTEntryIfExtendedE>), and you wish to influence
the inclusion of these container tags. For example, if a template contains
the container
<$MTEntryMore$>
and you wish to exclude this conditional, you could call I like this:
my $out = $build->build($ctx, $tokens, { EntryIfExtended => 0 });
=head1 ERROR HANDLING
On an error, the above methods return C, and the error message can
be obtained by calling the method I on the object. For example:
defined(my $out = $build->build($ctx, $tokens))
or die $build->errstr;
=head1 AUTHOR & COPYRIGHTS
Please see the I manpage for author, copyright, and license information.
=cut