File Coverage

blib/lib/HTML/Blitz/Builder.pm
Criterion Covered Total %
statement 83 84 98.8
branch 51 76 67.1
condition 6 12 50.0
subroutine 11 11 100.0
pod 5 5 100.0
total 156 188 82.9


line stmt bran cond sub pod time code
1             package HTML::Blitz::Builder;
2 2     2   234197 use HTML::Blitz::pragma;
  2         4  
  2         14  
3 2     2   878 use Exporter qw(import);
  2         4  
  2         63  
4 2     2   11 use Carp qw(croak);
  2         4  
  2         300  
5              
6             our $VERSION = '0.09';
7              
8             our @EXPORT_OK = qw(
9             mk_doctype
10             mk_comment
11             mk_elem
12             to_html
13             fuse_fragment
14             );
15              
16             {
17             package HTML::Blitz::Builder::_Fragment;
18              
19 3 50   3   11 method new($class: @parts) {
  3         17  
  3         11  
  3         4  
20 3         22 bless {
21             parts => \@parts,
22             }, $class
23             }
24              
25 3 50   3   11 method unwrapped() {
  3 50       11  
  3         7  
  3         5  
26 3         5 @{$self->{parts}}
  3         47  
27             }
28             }
29              
30 27     27 1 78 fun to_html(@parts) {
  27         37  
31 27 100       235 join '',
    100          
32 2 0       22 map ref($_) eq __PACKAGE__ ? $$_ : s{(?![\n\t])([[:cntrl:]&<])}{ $1 eq '<' ? '<' : $1 eq '&' ? '&' : '&#' . ord($1) . ';' }egr,
    50          
33             map ref($_) eq __PACKAGE__ . '::_Fragment' ? $_->unwrapped : $_,
34             @parts
35             }
36              
37 3     3 1 11 fun fuse_fragment(@parts) {
  3         7  
38 3 0 0     43 return $parts[0]
      33        
39             if @parts == 1 && (ref($parts[0]) eq __PACKAGE__ || ref($parts[0]) eq __PACKAGE__ . '::_Fragment');
40 3         20 HTML::Blitz::Builder::_Fragment->new(@parts)
41             }
42              
43 1 50   1 1 94 fun mk_doctype() {
  1         2  
44 1         5 my $code = '';
45 1         8 bless \$code, __PACKAGE__
46             }
47              
48 6 50   6 1 1836 fun mk_comment($content) {
  6 50       13  
  6         15  
  6         7  
49 6 100       287 $content =~ /\A(-?>)/
50             and croak "HTML comment cannot start with '$1': '$content'";
51 4 100       270 $content =~ /(";
54 1         6 bless \$code, __PACKAGE__
55             }
56              
57 2 50   2   9 fun _mk_attr($name, $value) {
  2 50       4  
  2         6  
  2         3  
58 2 50       19 $name =~ m{\A[^\s/>="'<[:cntrl:]]+\z}
59             or croak "Invalid attribute name '$name'";
60 2         7 my $code = " $name";
61 2 50       6 if ($value ne '') {
62 2         3 $code .= '=';
63 2 100       8 if ($value !~ m{[\s"'=<>`]}) {
64 1 0       3 $code .= $value =~ s{([[:cntrl:]&])}{ $1 eq '&' ? '&' : '&#' . ord($1) . ';' }egr;
  0         0  
65             } else {
66 1 50       7 $code .= '"' . $value =~ s{(?![\n\t])([[:cntrl:]&"])}{ $1 eq '"' ? '"' : $1 eq '&' ? '&' : '&#' . ord($1) . ';' }egr . '"';
  1 50       9  
67             }
68             }
69             $code
70 2         8 }
71              
72             my %is_void = map +($_ => 1), qw(
73             area
74             base basefont bgsound br
75             col
76             embed
77             frame
78             hr
79             img input
80             keygen
81             link
82             meta
83             param
84             source
85             track
86             wbr
87             );
88              
89             my %is_childless = map +($_ => 1), qw(
90             title
91             textarea
92             script
93             style
94             );
95              
96 31 50   31 1 9826 fun mk_elem($name, @args) {
  31         72  
  31         42  
97 31 100 100     143 my $attrs = @args && ref($args[0]) eq 'HASH'
98             ? shift @args
99             : {};
100 31 50       195 $name =~ m{\A[A-Za-z][^\s/>[:cntrl:]]*\z}
101             or croak "Invalid tag name '$name'";
102 31         68 (my $lc_name = $name) =~ tr/A-Z/a-z/;
103 31 50       119 @args = map ref($_) eq __PACKAGE__ . '::_Fragment' ? $_->unwrapped : $_, @args;
104 31         107 my $attr_str = join '', map _mk_attr($_, $attrs->{$_}), sort keys %$attrs;
105 31         76 my $html = "<$name$attr_str>";
106 31 100       82 if ($is_void{$lc_name}) {
107 3 50       11 croak "<$name> tag cannot have contents" if @args;
108             } else {
109 28 50 66     114 croak "<$name> tag cannot have child elements" if $is_childless{$lc_name} && grep ref($_) eq __PACKAGE__, @args;
110 28         43 my $contents;
111 28 100       76 if ($lc_name eq 'style') {
    100          
112 4         10 $contents = join '', @args;
113 4 100       202 $contents =~ m{(])}aai
114             and croak "<$name> tag cannot contain '$1'";
115             } elsif ($lc_name eq 'script') {
116 21         44 $contents = join '', @args;
117             SCRIPT_DATA: {
118 21 100       32 $contents =~ m{ ( ) | ( < (/?) script [ \t\r\n\f/>] ) }xaaigc
  11         40  
123             or last SCRIPT_DATA;
124 8 100       35 $1 and redo SCRIPT_DATA;
125 6 50       16 $3 and croak "<$name> tag cannot contain '$2'";
126              
127 6 100       103 $contents =~ m{ (-->) | ] }xaaigc
128             or croak "<$name> tag cannot contain '' or ''";
129 5 100       14 $1 and redo SCRIPT_DATA;
130 2         5 redo SCRIPT_DATA_ESCAPED;
131             }
132             }
133             } else {
134 3         8 $contents = to_html @args;
135             }
136 19         52 $html .= "$contents";
137             }
138 22         99 bless \$html, __PACKAGE__
139             }
140              
141             1
142             __END__