File Coverage

blib/lib/Text/Template/Simple/Tokenizer.pm
Criterion Covered Total %
statement 157 201 78.1
branch 70 90 77.7
condition 26 37 70.2
subroutine 27 35 77.1
pod 4 4 100.0
total 284 367 77.3


line stmt bran cond sub pod time code
1             package Text::Template::Simple::Tokenizer;
2 62     62   365 use strict;
  62         114  
  62         2130  
3 62     62   328 use warnings;
  62         102  
  62         2694  
4              
5             our $VERSION = '0.86';
6              
7 62     62   370 use constant CMD_CHAR => 0;
  62         106  
  62         4155  
8 62     62   362 use constant CMD_ID => 1;
  62         106  
  62         3009  
9 62     62   350 use constant CMD_CB => 2; # callbacks
  62         306  
  62         3109  
10 62     62   341 use constant ID_DS => 0;
  62         121  
  62         11943  
11 62     62   450 use constant ID_DE => 1;
  62         122  
  62         2994  
12 62     62   330 use constant ID_PRE_CHOMP => 2;
  62         122  
  62         2921  
13 62     62   337 use constant ID_POST_CHOMP => 3;
  62         112  
  62         2589  
14 62     62   349 use constant SUBSTR_OFFSET_FIRST => 0;
  62         131  
  62         2670  
15 62     62   332 use constant SUBSTR_OFFSET_SECOND => 1;
  62         114  
  62         3322  
16 62     62   331 use constant SUBSTR_LENGTH => 1;
  62         105  
  62         3297  
17              
18 62     62   359 use Text::Template::Simple::Util qw( LOG DEBUG fatal );
  62         128  
  62         15288  
19 62     62   415 use Text::Template::Simple::Constants qw( :all );
  62         127  
  62         283224  
20              
21             my @COMMANDS = ( # default command list
22             # cmd id
23             [ DIR_CAPTURE , T_CAPTURE ],
24             [ DIR_DYNAMIC , T_DYNAMIC, ],
25             [ DIR_STATIC , T_STATIC, ],
26             [ DIR_NOTADELIM, T_NOTADELIM ],
27             [ DIR_COMMENT , T_COMMENT ],
28             [ DIR_COMMAND , T_COMMAND ],
29             );
30              
31             sub new {
32 502     502 1 1096 my $class = shift;
33 502         1207 my $self = [];
34 502         2222 bless $self, $class;
35 502   33     7737 $self->[ID_DS] = shift || fatal('tts.tokenizer.new.ds');
36 502   33     8714 $self->[ID_DE] = shift || fatal('tts.tokenizer.new.de');
37 502   100     2813 $self->[ID_PRE_CHOMP] = shift || CHOMP_NONE;
38 502   100     2649 $self->[ID_POST_CHOMP] = shift || CHOMP_NONE;
39 502         1884 return $self;
40             }
41              
42             sub tokenize {
43             # compile the template into a tree and optimize
44 502     502 1 1670 my($self, $tmp, $map_keys) = @_;
45              
46 502 100       1337 return $self->_empty_token( $tmp ) if ! $tmp;
47              
48 498         1319 my($ds, $de) = ($self->[ID_DS], $self->[ID_DE]);
49 498         1438 my($qds, $qde) = map { quotemeta $_ } $ds, $de;
  996         5124  
50              
51 498         951 my(@tokens, $inside);
52              
53 498         9371 OUT_TOKEN: foreach my $i ( split /($qds)/xms, $tmp ) {
54              
55 1710 100       8743 if ( $i eq $ds ) {
56 606         3047 push @tokens, [ $i, T_DELIMSTART, [], undef ];
57 606         1442 $inside = 1;
58 606         1752 next OUT_TOKEN;
59             }
60              
61 1104         7811 IN_TOKEN: foreach my $j ( split /($qde)/xms, $i ) {
62 1592 100       4895 if ( $j eq $de ) {
63 604         4172 my $last_token = $tokens[LAST_TOKEN];
64 604 100       1507 if ( T_NOTADELIM == $last_token->[TOKEN_ID] ) {
65 20         97 $last_token->[TOKEN_STR] = $self->tilde(
66             $last_token->[TOKEN_STR] . $de
67             );
68             }
69             else {
70 584         2586 push @tokens, [ $j, T_DELIMEND, [], undef ];
71             }
72 604         2233 $inside = 0;
73 604         3145 next IN_TOKEN;
74             }
75 988         7113 push @tokens, $self->_token_code( $j, $inside, $map_keys, \@tokens );
76             }
77             }
78              
79 498 50       3590 $self->_debug_tokens( \@tokens ) if $self->can('DEBUG_TOKENS');
80              
81 498         3717 return \@tokens;
82             }
83              
84             sub tilde {
85 402     402 1 1275 my(undef, @args) = @_;
86 402         2784 return Text::Template::Simple::Util::escape( q{~} => @args );
87             }
88              
89             sub quote {
90 0     0 1 0 my(undef, @args) = @_;
91 0         0 return Text::Template::Simple::Util::escape( q{"} => @args )
92             }
93              
94             sub _empty_token {
95 4     4   7 my $self = shift;
96 4         9 my $tmp = shift;
97 4 50       15 fatal('tts.tokenizer.tokenize.tmp') if ! defined $tmp;
98             # empty string or zero
99             return [
100 4         50 [ $self->[ID_DS], T_DELIMSTART, [], undef ],
101             [ $tmp , T_RAW , [], undef ],
102             [ $self->[ID_DE], T_DELIMEND , [], undef ],
103             ]
104             }
105              
106             sub _get_command_chars {
107 988     988   1718 my($self, $str) = @_;
108 988         1716 my($first_cmd, $second_cmd, $last_cmd);
109             # $first is the left-cmd, $last is the right-cmd. $second is the extra
110 988 100       3759 $first_cmd = substr $str, SUBSTR_OFFSET_FIRST , SUBSTR_LENGTH if $str ne EMPTY_STRING;
111 988 100       5076 $second_cmd = substr $str, SUBSTR_OFFSET_SECOND, SUBSTR_LENGTH if $str ne EMPTY_STRING;
112 988 100       3957 $last_cmd = substr $str, length($str) - 1 , SUBSTR_LENGTH if $str ne EMPTY_STRING;
113 988   100     9408 return $first_cmd || EMPTY_STRING,
      100        
      100        
114             $second_cmd || EMPTY_STRING,
115             $last_cmd || EMPTY_STRING;
116             }
117              
118             sub _user_commands {
119 606     606   1380 my $self = shift;
120 606 50       4496 return +() if ! $self->can('commands');
121 0         0 return $self->commands;
122             }
123              
124             sub _token_for_command {
125 524     524   1458 my($self, $tree, $map_keys, $str, $last_cmd, $second_cmd, $cmd, $inside) = @_;
126 524         1861 my($copen, $cclose, $ctoken) = $self->_chomp_token( $second_cmd, $last_cmd );
127 524         1993 my $len = length $str;
128 524 50       1623 my $cb = $map_keys ? 'quote' : $cmd->[CMD_CB];
129 524 100       1350 my $soff = $copen ? 2 : 1;
130 524 100       1553 my $slen = $len - ($cclose ? $soff+1 : 1);
131 524         1252 my $buf = substr $str, $soff, $slen;
132              
133 524 100       2008 if ( T_NOTADELIM == $cmd->[CMD_ID] ) {
134 22         63 $buf = $self->[ID_DS] . $buf;
135 22         65 $tree->[LAST_TOKEN][TOKEN_ID] = T_DISCARD;
136             }
137              
138 524         1446 my $needs_chomp = defined $ctoken;
139 524 50       2516 $self->_chomp_prev($tree, $ctoken) if $needs_chomp;
140              
141 524 50       1332 my $id = $map_keys ? T_RAW : $cmd->[CMD_ID];
142 524 50       1443 my $val = $cb ? $self->$cb( $buf ) : $buf;
143              
144             return [
145 524 50       17745 $val,
146             $id,
147             [CHOMP_NONE, CHOMP_NONE],
148             $needs_chomp ? $ctoken : undef # trigger
149             ];
150             }
151              
152             sub _token_for_code {
153 82     82   251 my($self, $tree, $map_keys, $str, $last_cmd, $first_cmd) = @_;
154 82         330 my($copen, $cclose, $ctoken) = $self->_chomp_token( $first_cmd, $last_cmd );
155 82         168 my $len = length $str;
156 82 100       317 my $soff = $copen ? 1 : 0;
157 82 100       669 my $slen = $len - ( $cclose ? $soff+1 : 0 );
158              
159 82         153 my $needs_chomp = defined $ctoken;
160 82 50       490 $self->_chomp_prev($tree, $ctoken) if $needs_chomp;
161              
162             return [
163 82 100       876 substr($str, $soff, $slen),
    50          
164             $map_keys ? T_MAPKEY : T_CODE,
165             [ CHOMP_NONE, CHOMP_NONE ],
166             $needs_chomp ? $ctoken : undef # trigger
167             ];
168             }
169              
170             sub _token_code {
171 988     988   2307 my($self, $str, $inside, $map_keys, $tree) = @_;
172 988         3180 my($first_cmd, $second_cmd, $last_cmd) = $self->_get_command_chars( $str );
173              
174 988 100       3188 if ( $inside ) {
175 606         5292 my @common = ($tree, $map_keys, $str, $last_cmd);
176 606         2564 foreach my $cmd ( @COMMANDS, $self->_user_commands ) {
177 1404 100       18008 next if $first_cmd ne $cmd->[CMD_CHAR];
178 524         2084 return $self->_token_for_command( @common, $second_cmd, $cmd, $inside );
179             }
180 82         380 return $self->_token_for_code( @common, $first_cmd );
181             }
182              
183 382         1713 my $prev = $tree->[PREVIOUS_TOKEN];
184              
185             return [
186 382 100       10448 $self->tilde( $str ),
187             T_RAW,
188             [ $prev ? $prev->[TOKEN_TRIGGER] : undef, CHOMP_NONE ],
189             undef # trigger
190             ];
191             }
192              
193             sub _chomp_token {
194 606     606   1710 my($self, $open_tok, $close_tok) = @_;
195 606         2515 my($pre, $post) = ( $self->[ID_PRE_CHOMP], $self->[ID_POST_CHOMP] );
196 606         3672 my $c = CHOMP_NONE;
197              
198             my $copen = $open_tok eq DIR_CHOMP_NONE ? RESET_FIELD
199 8         11 : $open_tok eq DIR_COLLAPSE ? do { $c |= COLLAPSE_LEFT; 1 }
  8         13  
200 4         8 : $pre & COLLAPSE_ALL ? do { $c |= COLLAPSE_LEFT; 1 }
  4         9  
201 4         7 : $pre & CHOMP_ALL ? do { $c |= CHOMP_LEFT; 1 }
  4         10  
202 606 100       5129 : $open_tok eq DIR_CHOMP ? do { $c |= CHOMP_LEFT; 1 }
  10 100       13  
  10 100       20  
    100          
    50          
203             : 0
204             ;
205              
206             my $cclose = $close_tok eq DIR_CHOMP_NONE ? RESET_FIELD
207 6         20 : $close_tok eq DIR_COLLAPSE ? do { $c |= COLLAPSE_RIGHT; 1 }
  6         15  
208 4         7 : $post & COLLAPSE_ALL ? do { $c |= COLLAPSE_RIGHT; 1 }
  4         8  
209 4         7 : $post & CHOMP_ALL ? do { $c |= CHOMP_RIGHT; 1 }
  4         7  
210 606 100       4243 : $close_tok eq DIR_CHOMP ? do { $c |= CHOMP_RIGHT; 1 }
  32 100       53  
  32 100       58  
    100          
    50          
211             : 0
212             ;
213              
214 606   100     2066 my $cboth = $copen > 0 && $cclose > 0;
215              
216 606 100 100     1950 $c |= COLLAPSE_ALL if( ($c & COLLAPSE_LEFT) && ($c & COLLAPSE_RIGHT) );
217 606 100 100     2254 $c |= CHOMP_ALL if( ($c & CHOMP_LEFT ) && ($c & CHOMP_RIGHT ) );
218              
219 606   100     3520 return $copen, $cclose, $c || CHOMP_NONE;
220             }
221              
222             sub _chomp_prev {
223 606     606   1246 my($self, $tree, $ctoken) = @_;
224 606   100     5694 my $prev = $tree->[PREVIOUS_TOKEN] || return; # no previous if this is first
225 302 100       1771 return if T_RAW != $prev->[TOKEN_ID]; # only RAWs can be chomped
226              
227 300         640 my $tc_prev = $prev->[TOKEN_CHOMP][TOKEN_CHOMP_PREV];
228 300         993 my $tc_next = $prev->[TOKEN_CHOMP][TOKEN_CHOMP_NEXT];
229              
230 300 100       1670 $prev->[TOKEN_CHOMP] = [
    50          
231             $tc_next ? $tc_next : CHOMP_NONE,
232             $tc_prev ? $tc_prev | $ctoken : $ctoken
233             ];
234 300         854 return;
235             }
236              
237             sub _get_symbols {
238             # fetch the related constants
239 0     0   0 my $self = shift;
240 0   0     0 my $regex = shift || fatal('tts.tokenizer._get_symbols.regex');
241 62     62   677 no strict qw( refs );
  62         156  
  62         65704  
242 0         0 return grep { $_ =~ $regex } keys %{ ref($self) . q{::} };
  0         0  
  0         0  
243             }
244              
245             sub _visualize_chomp {
246 0     0   0 my $self = shift;
247 0         0 my $param = shift;
248 0 0       0 return 'undef' if ! defined $param;
249              
250 0         0 my @test = map { $_->[0] }
  0         0  
251 0         0 grep { $param & $_->[1] }
252 0         0 map { [ $_, $self->$_() ] }
253             $self->_get_symbols( qr{ \A (?: CHOMP|COLLAPSE ) }xms );
254              
255 0 0       0 return @test ? join( q{,}, @test ) : 'undef';
256             }
257              
258             sub _visualize_tid {
259 0     0   0 my $self = shift;
260 0         0 my $id = shift;
261 0         0 my @ids = (
262             undef,
263 0         0 sort { $self->$a() <=> $self->$b() }
264 0         0 grep { $_ ne 'T_MAXID' }
265             $self->_get_symbols( qr{ \A (?: T_ ) }xms )
266             );
267              
268 0   0     0 my $rv = $ids[ $id ] || ( defined $id ? $id : 'undef' );
269 0         0 return $rv;
270             }
271              
272             sub _visualize_ws {
273 0     0   0 my $self = shift;
274 0         0 my $str = shift;
275 0         0 $str =~ s{\r}{\\r}xmsg;
276 0         0 $str =~ s{\n}{\\n}xmsg;
277 0         0 $str =~ s{\f}{\\f}xmsg;
278 0         0 $str =~ s{\s}{\\s}xmsg;
279 0         0 return $str;
280             }
281              
282             sub _debug_tokens {
283 0     0   0 my $self = shift;
284 0         0 my $tokens = shift;
285 0         0 my $buf = $self->_debug_tokens_head;
286              
287 0         0 foreach my $t ( @{ $tokens } ) {
  0         0  
288 0 0       0 $buf .= $self->_debug_tokens_row(
289             $self->_visualize_tid( $t->[TOKEN_ID] ),
290             $self->_visualize_ws( $t->[TOKEN_STR] ),
291 0         0 map { $_ eq 'undef' ? EMPTY_STRING : $_ }
292 0         0 map { $self->_visualize_chomp( $_ ) }
293             $t->[TOKEN_CHOMP][TOKEN_CHOMP_NEXT],
294             $t->[TOKEN_CHOMP][TOKEN_CHOMP_PREV],
295             $t->[TOKEN_TRIGGER]
296             );
297             }
298 0         0 Text::Template::Simple::Util::LOG( DEBUG => $buf );
299 0         0 return;
300             }
301              
302             sub _debug_tokens_head {
303 0     0   0 my $self = shift;
304 0         0 return <<'HEAD';
305              
306             ---------------------------
307             TOKEN DUMP
308             ---------------------------
309             HEAD
310             }
311              
312             sub _debug_tokens_row {
313 0     0   0 my($self, @params) = @_;
314 0         0 return sprintf <<'DUMP', @params;
315             ID : %s
316             STRING : %s
317             CHOMP_NEXT: %s
318             CHOMP_PREV: %s
319             TRIGGER : %s
320             ---------------------------
321             DUMP
322             }
323              
324             sub DESTROY {
325 502   50 502   9015 my $self = shift || return;
326 502 50       1706 LOG( DESTROY => ref $self ) if DEBUG;
327 502         2997 return;
328             }
329              
330             1;
331              
332             __END__
333              
334             =head1 NAME
335              
336             Text::Template::Simple::Tokenizer - Tokenizer
337              
338             =head1 SYNOPSIS
339              
340             use strict;
341             use constant TYPE => 0;
342             use constant DATA => 1;
343             use Text::Template::Simple::Tokenize;
344             my $t = Text::Template::Simple::Tokenize->new( $start_delim, $end_delim );
345             my $tokens = $t->tokenize( $raw_data );
346             foreach my $token ( @{ $tokens } ) {
347             printf "Token type: %s\n", $token->[TYPE];
348             printf "Token data: %s\n", $token->[DATA];
349             }
350              
351             =head1 DESCRIPTION
352              
353             This document describes version C<0.86> of C<Text::Template::Simple::Tokenizer>
354             released on C<5 March 2012>.
355              
356             Tokenizes the input with the defined delimiter pair.
357              
358             =head1 METHODS
359              
360             =head2 new
361              
362             The object constructor. Accepts two parameters in this order:
363             C<start_delimiter> and C<end_delimiter>.
364              
365             =head2 tokenize
366              
367             Tokenizes the input with the supplied delimiter pair. Accepts a single
368             parameter: the raw template string.
369              
370             =head2 ESCAPE METHODS
371              
372             =head2 tilde
373              
374             Escapes the tilde character.
375              
376             =head3 quote
377              
378             Escapes double quotes.
379              
380             =head1 AUTHOR
381              
382             Burak Gursoy <burak@cpan.org>.
383              
384             =head1 COPYRIGHT
385              
386             Copyright 2004 - 2012 Burak Gursoy. All rights reserved.
387              
388             =head1 LICENSE
389              
390             This library is free software; you can redistribute it and/or modify
391             it under the same terms as Perl itself, either Perl version 5.12.3 or,
392             at your option, any later version of Perl 5 you may have available.
393              
394             =cut