File Coverage

blib/lib/MobaSiF/Template/Compiler.pm
Criterion Covered Total %
statement 12 256 4.6
branch 0 168 0.0
condition 0 12 0.0
subroutine 4 11 36.3
pod 0 5 0.0
total 16 452 3.5


line stmt bran cond sub pod time code
1             package MobaSiF::Template::Compiler;
2              
3 1     1   25 use 5.008;
  1         4  
  1         40  
4 1     1   82 use strict;
  1         4  
  1         37  
5 1     1   2169 use FileHandle;
  1         59527  
  1         6  
6             use constant {
7            
8             # タイプID
9            
10 1         28172 TYPE_PLAIN => 1,
11             TYPE_REPLACE => 2,
12             TYPE_LOOP => 3,
13             TYPE_IF => 4,
14             TYPE_ELSE => 5,
15             TYPE_QSA => 6,
16             TYPE_LB => 253,
17             TYPE_RB => 254,
18             TYPE_END => 255,
19            
20             # オプション値
21            
22             O_ENCODE => 1, # url encode
23             O_HSCHRS => 2, # htmlspecialchars
24             O_NL2BR => 4, # nl2br
25             O_SUBSTR => 8, # substr
26            
27             # デリミタ
28            
29             DELIM_OR => '\\|+',
30             DELIM_AND => '\\&+',
31            
32             # 条件タイプ
33            
34             COND_EQ => 0,
35             COND_NE => 1,
36             COND_GT => 2,
37             COND_GE => 3,
38             COND_LT => 4,
39             COND_LE => 5,
40            
41             # その他
42            
43             TRUE => 1,
44             FALSE => 0,
45 1     1   548 };
  1         2  
46              
47             our $VERSION = '0.03';
48              
49             #---------------------------------------------------------------------
50              
51             sub loadTemplate {
52 0     0 0   my ($in) = @_;
53            
54 0           my $tpl;
55 0 0         if (ref($in)) {
56             # ファイル名ではなくて文字列参照から生成する場合
57 0           $tpl = ${$in};
  0            
58             } else {
59 0           my $fh = new FileHandle;
60 0 0         open($fh, $in) || die "Can't find template $in\n";
61 0           $tpl = join('', <$fh>);
62 0           close($fh);
63             }
64 0           return _parseTemplate(\$tpl);
65             }
66              
67             sub _parseTemplate {
68 0     0     my ($rTpl) = @_;
69 0           my $i;
70            
71             my @parts;
72 0           my $pos = 0;
73            
74             # Vodafone 絵文字(SJIS)がテンプレに入っていると
75             # 悪影響を与えるのでいったんエスケープ
76            
77 0           my $voda_esc1 = chr(0x1B).chr(0x24);
78 0           my $voda_esc2 = chr(0x0F);
79 0           my $voda_esc_q = quotemeta($voda_esc1). '(.*?)'. quotemeta($voda_esc2);
80            
81 0           ${$rTpl} =~ s($voda_esc_q) {
  0            
82 0           my $in = $1;
83 0           $in =~ s/./unpack('H2',$&)/eg;
  0            
84 0           ('%%ESC%%'. $in. '%%/ESC%%');
85             }eg;
86            
87 0           ${$rTpl} =~ s(\t*\$(\s*([\=\{\}]|if|loop|/?qsa|.)[^\$]*)\$\t*|[^\$]+) {
  0            
88 0 0         if (!(my $cmd = $1)) {
89            
90             #-----------------
91             # PLAIN
92            
93 0           my $text = $&;
94 0           $text =~ s(\%\%ESC\%\%(.*?)\%\%/ESC\%\%) {
95 0           my $in = $1;
96 0           $in =~ s/[a-f\d]{2}/pack("C", hex($&))/egi;
  0            
97 0           ($voda_esc1. $in. $voda_esc2);
98             }eg;
99 0           push(@parts, { type => TYPE_PLAIN, text => $text }); $pos++;
  0            
100            
101             } else {
102            
103 0           my $cmd_orig = $cmd;
104 0           $cmd =~ s/\s+//g;
105            
106             #-----------------
107             # REPLACE
108            
109 0 0         if ($cmd =~ /^\=((b|e|h|hn)\:)?/i) {
    0          
    0          
    0          
    0          
    0          
110 0           my ($l, $o, $key) = ('', "$2", "$'");
111            
112 0 0         die "no replace type '$cmd_orig'\n" if ($o eq '');
113            
114 0           my $opt = 0;
115 0 0         $opt = O_ENCODE if ($o eq 'e');
116 0 0         $opt = O_HSCHRS if ($o eq 'h');
117 0 0         $opt = O_HSCHRS | O_NL2BR if ($o eq 'hn');
118            
119 0           push(@parts, { type => TYPE_REPLACE,
120 0           key => $key, opt => $opt }); $pos++;
121             }
122            
123             #-----------------
124             # LOOP
125            
126             elsif ($cmd =~ /^loop\(([^\)]+)\)\{$/i) {
127 0           my $key = $1;
128 0           push(@parts, { type => TYPE_LOOP,
129 0           key => $key, loopend => $pos + 1 }); $pos++;
130 0           push(@parts, { type => TYPE_LB }); $pos++;
  0            
131             }
132            
133             #-----------------
134             # [ELS]IF -> [RB + ELSE +] IF + LB
135            
136             elsif ($cmd =~ /^(\}els)?if\(([^\)]+)\)\{$/i) {
137 0           my $else = $1;
138 0           my $cond = $2;
139 0 0         my $delim = ($cond =~ /\|/) ? DELIM_OR : DELIM_AND;
140 0           my @p = split($delim, $cond);
141 0           my $ofs_next = scalar(@p);
142            
143 0 0         if ($else) {
144 0           $ofs_next++;
145 0           push(@parts, { type => TYPE_RB }); $pos++;
  0            
146 0           push(@parts, { type => TYPE_ELSE,
147             ontrue => $pos + 1, onfalse => $pos + $ofs_next });
148 0           $pos++; $ofs_next--;
  0            
149             }
150 0           for my $p (@p) {
151 0 0         if ($delim eq DELIM_AND) {
152 0           push(@parts, { type => TYPE_IF,
153             ontrue => $pos + 1, onfalse => $pos + $ofs_next,
154             cond => $p });
155             } else {
156 0           push(@parts, { type => TYPE_IF,
157             ontrue => $pos + $ofs_next, onfalse => $pos + 1,
158             cond => $p });
159             }
160 0           $pos++; $ofs_next--;
  0            
161             }
162 0           push(@parts, { type => TYPE_LB }); $pos++;
  0            
163             }
164            
165             #-----------------
166             # ELSE -> RB + ELSE + LB
167            
168             elsif ($cmd =~ /^\}else\{$/i) {
169 0           push(@parts, { type => TYPE_RB }); $pos++;
  0            
170 0           push(@parts, { type => TYPE_ELSE,
171 0           ontrue => $pos + 1, onfalse => $pos + 1 }); $pos++;
172 0           push(@parts, { type => TYPE_LB }); $pos++;
  0            
173             }
174            
175             #-----------------
176             # RB
177            
178             elsif ($cmd =~ /^\}$/i) {
179 0           push(@parts, { type => TYPE_RB }); $pos++;
  0            
180             }
181            
182             #-----------------
183             # QSA
184            
185             elsif ($cmd =~ /^(\/)?qsa$/i) {
186 0 0         push(@parts, { type => TYPE_QSA, inout => $1 ? 1 : 0 }); $pos++;
  0            
187             }
188            
189             #-----------------
190             # ERROR
191            
192             else {
193 0           die "Unknown command \$$cmd_orig\$\n";
194             }
195             }
196             }egisx;
197 0           push(@parts, { type => TYPE_END });
198            
199 0 0         if (${$rTpl} =~ /\$/) {
  0            
200 0           die "unmatched '\$' found\n";
201             }
202            
203             # 括弧の対応関係を設定
204            
205 0           $i = 0;
206 0           my @stack;
207 0           for my $raPart (@parts) {
208 0 0         if ($raPart->{type} == TYPE_LB) {
    0          
209 0           push(@stack, $i);
210             }
211             elsif ($raPart->{type} == TYPE_RB) {
212 0           $parts[pop(@stack)]->{rbpos} = $i;
213             }
214 0           $i++;
215             }
216            
217             # 各条件部の飛び先を正しく設定
218            
219 0           for my $raPart (@parts) {
220 0 0 0       if ($raPart->{type} == TYPE_IF ||
    0          
221             $raPart->{type} == TYPE_ELSE) {
222 0 0         if ($parts[$raPart->{onfalse}]->{type} == TYPE_LB) {
223 0           $raPart->{onfalse} =
224             $parts[$raPart->{onfalse}]->{rbpos};
225             }
226             } elsif ($raPart->{type} == TYPE_LOOP) {
227 0           $raPart->{loopend} =
228             $parts[$raPart->{loopend}]->{rbpos};
229 0           $parts[$raPart->{loopend}]->{type} = TYPE_END;
230             }
231             }
232            
233             # 括弧の対応関係をチェック
234            
235             {
236 0           my $lv = 1;
  0            
237 0           for my $raPart (@parts) {
238 0 0 0       if ($raPart->{type} == TYPE_LB) {
    0          
239 0           $lv++;
240             } elsif
241             ($raPart->{type} == TYPE_RB ||
242             $raPart->{type} == TYPE_END ) {
243 0           $lv--;
244 0 0         if ($lv < 0) {
245 0           die "unmatched {}\n";
246             }
247             }
248             }
249 0 0         if ($lv != 0) {
250 0           die "unmatched {}\n";
251             }
252             }
253            
254             # 条件部を生成
255            
256 0           for my $raPart (@parts) {
257 0 0         if ($raPart->{type} == TYPE_IF) {
258 0           my $cond_str = $raPart->{cond};
259 0 0         if ($cond_str =~ />(\=)?/) {
    0          
    0          
    0          
260 0           my $val = int($');
261 0           $raPart->{condkey} = $`;
262 0           $raPart->{condval} = $val;
263 0 0         $raPart->{condtyp} = $1 ? COND_GE : COND_GT;
264             } elsif ($cond_str =~ /<(\=)?/) {
265 0           my $val = int($');
266 0           $raPart->{condkey} = $`;
267 0           $raPart->{condval} = $val;
268 0 0         $raPart->{condtyp} = $1 ? COND_LE : COND_LT;
269             } elsif ($cond_str =~ /^\!/) {
270 0           $raPart->{condkey} = $';
271 0           $raPart->{condval} = '';
272 0           $raPart->{condtyp} = COND_EQ;
273             } elsif ($cond_str =~ /(\!)?==?/) {
274 0           $raPart->{condkey} = $`;
275 0           $raPart->{condval} = $';
276 0 0         $raPart->{condtyp} = $1 ? COND_NE : COND_EQ;
277             } else {
278 0           $raPart->{condkey} = $cond_str;
279 0           $raPart->{condval} = '';
280 0           $raPart->{condtyp} = COND_NE;
281             }
282             }
283             }
284            
285 0           return(\@parts);
286             }
287              
288             #=====================================================================
289             # バイナリテンプレート生成
290             #=====================================================================
291              
292             sub compile {
293 0     0 0   my ($in, $out_file) = @_;
294            
295 0           my $raParts = loadTemplate($in);
296            
297             # 行オフセットの計算
298            
299             {
300 0           my $ofs = 0;
  0            
301 0           for my $raPart (@{$raParts}) {
  0            
302 0           $raPart->{ofs} = $ofs;
303            
304 0           my $type = $raPart->{type};
305 0 0         if ( $type == TYPE_PLAIN ) { $ofs += 8; }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
306 0           elsif ( $type == TYPE_REPLACE ) { $ofs += 12; }
307 0           elsif ( $type == TYPE_IF ) { $ofs += 24; }
308 0           elsif ( $type == TYPE_ELSE ) { $ofs += 12; }
309 0           elsif ( $type == TYPE_LOOP ) { $ofs += 12; }
310 0           elsif ( $type == TYPE_QSA ) { $ofs += 8; }
311 0           elsif ( $type == TYPE_LB ) { $ofs += 4; }
312 0           elsif ( $type == TYPE_RB ) { $ofs += 4; }
313 0           elsif ( $type == TYPE_END ) { $ofs += 4; }
314             }
315             }
316            
317             # ジャンプ先参照位置の修正
318            
319             {
320 0           for my $raPart (@{$raParts}) {
  0            
  0            
321 0           my $type = $raPart->{type};
322 0 0         if ($type == TYPE_LOOP) {
    0          
    0          
323 0           $raPart->{loopend} = $raParts->[ $raPart->{loopend} ]->{ofs};
324             }
325             elsif ($type == TYPE_IF) {
326 0           $raPart->{ontrue} = $raParts->[ $raPart->{ontrue} ]->{ofs};
327 0           $raPart->{onfalse} = $raParts->[ $raPart->{onfalse} ]->{ofs};
328             }
329             elsif ($type == TYPE_ELSE) {
330 0           $raPart->{ontrue} = $raParts->[ $raPart->{ontrue} ]->{ofs};
331 0           $raPart->{onfalse} = $raParts->[ $raPart->{onfalse} ]->{ofs};
332             }
333             }
334             }
335            
336             # 文字列参照バッファ位置の設定
337            
338 0           my $strBuf = "";
339 0           my %strPos = ();
340 0           for my $raPart (@{$raParts}) {
  0            
341 0           my $type = $raPart->{type};
342 0 0         if ($type == TYPE_PLAIN) {
    0          
    0          
    0          
343 0           $raPart->{text} =
344             useStringPos(\$strBuf, \%strPos, $raPart->{text});
345             }
346             elsif ($type == TYPE_REPLACE) {
347 0           $raPart->{key} =
348             useStringPos(\$strBuf, \%strPos, $raPart->{key});
349             }
350             elsif ($type == TYPE_IF) {
351 0           $raPart->{condkey} =
352             useStringPos(\$strBuf, \%strPos, $raPart->{condkey});
353 0 0 0       if ($raPart->{condtyp} == COND_EQ ||
354             $raPart->{condtyp} == COND_NE) {
355 0           $raPart->{condval} =
356             useStringPos(\$strBuf, \%strPos, $raPart->{condval});
357             }
358             }
359             elsif ($type == TYPE_LOOP) {
360 0           $raPart->{key} =
361             useStringPos(\$strBuf, \%strPos, $raPart->{key});
362             }
363             }
364            
365             # 出力
366            
367 0 0         if ($out_file) {
368 0           my $fh = new FileHandle;
369 0           my $bin = makeBinTemplate($raParts, \$strBuf);
370 0 0         open($fh, ">$out_file") || die "Can't open $out_file";
371 0           print $fh $bin;
372 0           close($fh);
373             } else {
374 0           debugBinTemplate($raParts, \$strBuf);
375             }
376             }
377              
378             sub useStringPos {
379 0     0 0   my ($rStrBuf, $rhStrPos, $str) = @_;
380            
381 0 0         if (exists($rhStrPos->{$str})) {
382 0           return($rhStrPos->{$str});
383             }
384 0           my $newPos = length(${$rStrBuf});
  0            
385 0           $rhStrPos->{$str} = $newPos;
386 0           ${$rStrBuf} .= ($str. chr(0));
  0            
387 0           return($newPos);
388             }
389              
390             #-------------------------
391             # バイナリ化
392              
393             sub makeBinTemplate {
394 0     0 0   my ($raParts, $rStrBuf) = @_;
395 0           my $bin = '';
396            
397 0           for my $raPart (@{$raParts}) {
  0            
398 0           my $type = $raPart->{type};
399            
400 0 0         if ($type == TYPE_PLAIN) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
401 0           $bin .= pack('LL', $type,
402             $raPart->{text});
403             }
404             elsif ($type == TYPE_REPLACE) {
405 0           $bin .= pack('LLL', $type,
406             $raPart->{key},
407             $raPart->{opt});
408             }
409             elsif ($type == TYPE_LOOP) {
410 0           $bin .= pack('LLL', $type,
411             $raPart->{key},
412             $raPart->{loopend});
413             }
414             elsif ($type == TYPE_IF) {
415 0           $bin .= pack('LLLLLL', $type,
416             $raPart->{ontrue},
417             $raPart->{onfalse},
418             $raPart->{condkey},
419             $raPart->{condval},
420             $raPart->{condtyp});
421             }
422             elsif ($type == TYPE_ELSE) {
423 0           $bin .= pack('LLL', $type,
424             $raPart->{ontrue},
425             $raPart->{onfalse});
426             }
427             elsif ($type == TYPE_QSA) {
428 0           $bin .= pack('LL', $type, $raPart->{inout});
429             }
430             elsif ($type == TYPE_LB) {
431 0           $bin .= pack('L', $type);
432             }
433             elsif ($type == TYPE_RB) {
434 0           $bin .= pack('L', $type);
435             }
436             elsif ($type == TYPE_END) {
437 0           $bin .= pack('L', $type);
438             }
439             else {
440 0           die "unknown type ($type)\n";
441             }
442             }
443 0           return(pack('L', length($bin)). $bin. ${$rStrBuf});
  0            
444             }
445              
446             #-------------------------
447             # テンプレートの解析結果のデバッグ出力
448              
449             sub debugBinTemplate {
450 0     0 0   my ($raParts, $rStrBuf) = @_;
451            
452 0           print " :{\n";
453 0           for my $raPart (@{$raParts}) {
  0            
454 0           my $type = $raPart->{type};
455            
456 0           printf("%5d:", $raPart->{ofs});
457            
458 0 0         if ($type == TYPE_PLAIN) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
459 0           my $s = _debug_getString($rStrBuf, $raPart->{text});
460 0           $s =~ s/\s+/ /g;
461 0           print qq|"$s"|;
462             }
463             elsif ($type == TYPE_REPLACE) {
464 0           my @opt;
465 0 0         push(@opt, "e") if ($raPart->{opt} & O_ENCODE);
466 0 0         push(@opt, "h") if ($raPart->{opt} & O_HSCHRS);
467 0 0         push(@opt, "n") if ($raPart->{opt} & O_NL2BR);
468 0 0         my $opt = scalar(@opt) ? join ('', @opt) : '';
469 0           my $s = _debug_getString($rStrBuf, $raPart->{key});
470 0           print qq|=$opt:$s|;
471             }
472             elsif ($type == TYPE_LOOP) {
473 0           my $s = _debug_getString($rStrBuf, $raPart->{key});
474 0           print qq|loop (\@$s) loopend L$raPart->{loopend}|;
475             }
476             elsif ($type == TYPE_IF) {
477 0           my $cmp = '';
478 0 0         $cmp = '==' if ($raPart->{condtyp} == COND_EQ);
479 0 0         $cmp = '!=' if ($raPart->{condtyp} == COND_NE);
480 0 0         $cmp = '>' if ($raPart->{condtyp} == COND_GT);
481 0 0         $cmp = '<' if ($raPart->{condtyp} == COND_LT);
482 0 0         $cmp = '>=' if ($raPart->{condtyp} == COND_GE);
483 0 0         $cmp = '<=' if ($raPart->{condtyp} == COND_LE);
484 0           my $s1 = _debug_getString($rStrBuf, $raPart->{condkey});
485 0           my $s2 = $raPart->{condval};
486 0 0 0       my $s2 =
487             ($raPart->{condtyp} == COND_EQ ||
488             $raPart->{condtyp} == COND_NE) ?
489             "'". _debug_getString($rStrBuf, $raPart->{condval}). "'" :
490             $raPart->{condval};
491 0           print qq|if ( $s1 $cmp $s2 ) L$raPart->{ontrue} else L$raPart->{onfalse}|;
492             }
493             elsif ($type == TYPE_ELSE) {
494 0           print qq|if ( PREV_COND_IS_FALSE ) L$raPart->{ontrue} else L$raPart->{onfalse}|;
495             }
496             elsif ($type == TYPE_LB) {
497 0           print qq|{|;
498             }
499             elsif ($type == TYPE_RB) {
500 0           print qq|}|;
501             }
502             elsif ($type == TYPE_END) {
503 0           print qq|} END|;
504             }
505 0           print "\n";
506             }
507             }
508             sub _debug_getString {
509 0     0     my ($rStrBuf, $pos) = @_;
510 0           my $str = substr(${$rStrBuf}, $pos);
  0            
511 0           my $delim = chr(0);
512 0 0         $str = $` if ($str =~ /$delim/);
513 0           return($str);
514             }
515              
516             #=====================================================================
517              
518             1;
519              
520             __END__