File Coverage

blib/lib/Typist/Builder.pm
Criterion Covered Total %
statement 9 91 9.8
branch 0 34 0.0
condition 0 14 0.0
subroutine 3 11 27.2
pod 3 8 37.5
total 15 158 9.4


line stmt bran cond sub pod time code
1             package Typist::Builder;
2 1     1   1059 use strict;
  1         1  
  1         30  
3              
4 1     1   1226 use Tie::IxHash;
  1         5073  
  1         27  
5 1     1   7 use base qw( Class::ErrorHandler );
  1         1  
  1         732  
6              
7             my $PREFIX = Typist->instance->prefix;
8             my $START = "$PREFIX.+";
9             my $END = "/$PREFIX.+";
10             my $EMPTY = "\\\$$PREFIX.+\\\$";
11             my $TAG = "<(?:(?:$START)?|(?:$END)?|(?:$EMPTY)?)>";
12             my $TEXT = "(?:(?!$TAG).|\n)*";
13             my $TOK = "$TAG|$TEXT";
14              
15 0     0 1   sub new { bless {}, $_[0] }
16              
17             sub compile {
18 0     0 1   my $build = shift;
19 0           my ($ctx, $text) = @_;
20 0           my $out;
21 0           eval {
22 0           $build->start_document;
23 0           while ((my $tok) = $text =~ m/$TOK/gs) {
24 0 0         if ($tok =~ /$TAG/) {
25 0           $tok =~ s{(<[$/])?$PREFIX(.*)$?>}{$2};
26 0           my $type = $1;
27 0           my ($tag, $args) = split /\s+/, $tok, 2;
28 0   0       $args ||= '';
29 0           my %args;
30 0 0         if ($args) {
31 0           tie %args, "Tie::IxHash"; # maintain order.
32 0           while ($args =~ m{(\w+)\s*=\s*(["'])(.*?)\2/gs}) {
33 0           $args{$1} = $3;
34             }
35             }
36 0 0         if ($type eq '<') {
    0          
37 0           $build->start_element($tag, \%args);
38             } elsif ($type eq '<$') {
39 0           $build->start_element($tag, \%args);
40 0           $build->end_element($tag);
41             } else { # assume end tag
42 0           $build->end_element($tag);
43             }
44             } else { # TEXT
45 0           $build->characters($tok);
46             }
47             }
48 0           $out = $build->end_document;
49             };
50 0 0         $@ ? $build->error($@) : $out;
51              
52             }
53              
54             sub build {
55 0     0 1   my $build = shift;
56 0           my ($ctx, $tokens, $cond) = @_;
57 0   0       $cond ||= {};
58 0           $ctx->('builder', $build);
59 0 0         $ctx->stash('root', $tokens) unless $ctx->stash('root');
60 0           my $res = '';
61 0           my $ph = $ctx->post_process_handler;
62 0           for my $t (@$tokens) {
63 0 0         if ($t->[0] eq 'TEXT') {
64 0           $res .= $t->[1];
65             } else {
66 0           my ($tokens, $tokens_else);
67 0           my ($tag, $args, $children) = @$t;
68 0 0 0       if (exists $cond->{$tag} && !$cond->{$tag}) {
    0 0        
69 0           for my $child (@$children) {
70 0 0         if ($child->[0] eq 'Else') {
71 0           $tokens = $child->[2];
72 0           last;
73             }
74             }
75 0 0         next unless $tokens;
76             } elsif ($children && ref($children) eq 'ARRAY') {
77 0           for my $child (@$children) {
78 0 0         if ($child->[0] eq 'Else') {
79 0           push @$tokens_else, $child;
80             } else {
81 0           push @$tokens, $child;
82             }
83             }
84             }
85 0           my ($h) = $ctx->handler_for($tag);
86 0 0         if ($h) {
87 0           $ctx->stash('tag', $tag);
88 0           $ctx->stash('tokens', $tokens);
89 0           $ctx->stash('tokens_else', $tokens_else);
90 0           my $out = $h->($ctx, $args, $cond);
91 0 0         return $build->error("Error in <$PREFIX$tag>: " . $ctx->errstr)
92             unless defined $out;
93 0 0         $out = $ph->($ctx, $args, $out) if $ph;
94 0           $res .= $out;
95             } # here is where we could process unknown tag errors. add strict mode.
96             }
97             }
98 0           $res;
99             }
100              
101             #--- compile handlers
102              
103 0     0 0   sub start_document { $_[0]->{__stack} = [[]]; }
104              
105             sub start_element {
106 0     0 0   my ($build, $tag, $args) = @_;
107 0           my $parent = $build->{__stack}->[-1];
108 0   0       $parent->[2] ||= [];
109 0           my $e = [$tag, $args];
110 0           push @{$parent->[2]}, $e;
  0            
111 0           push @{$build->{__stack}}, $e;
  0            
112             }
113              
114             sub characters {
115 0     0 0   my ($build, $text) = @_;
116 0           my $parent = $build->{__stack}->[-1];
117 0   0       $parent->[2] ||= [];
118 0           push @{$parent->[2]}, $text;
  0            
119             }
120              
121             sub end_element {
122 0     0 0   my ($build, $tag) = @_;
123 0           my $e = pop @{$_[0]->{__stack}};
  0            
124 0 0         die Typist->translate("[_1] is missing a closing tag.", "<$PREFIX$tag>")
125             if $e->[0] ne $tag;
126             }
127              
128             sub end_document {
129 0     0 0   my $build = shift;
130 0           my $root = pop @{$build->{__stack}};
  0            
131 0           die Typist->translate('Elements left on the build stack.')
132 0 0         if scalar @{$build->{__stack}}; # localize!
133 0           $build->{__stack} = undef;
134 0           $root;
135             }
136              
137             1;
138              
139             __END__