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   230685 use HTML::Blitz::pragma;
  2         11  
  2         19  
3 2     2   829 use Exporter qw(import);
  2         7  
  2         68  
4 2     2   10 use Carp qw(croak);
  2         3  
  2         293  
5              
6             our $VERSION = '0.07';
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         7  
  3         9  
  3         6  
20 3         22 bless {
21             parts => \@parts,
22             }, $class
23             }
24              
25 3 50   3   10 method unwrapped() {
  3 50       11  
  3         5  
  3         4  
26 3         5 @{$self->{parts}}
  3         46  
27             }
28             }
29              
30 27     27 1 79 fun to_html(@parts) {
  27         35  
31 27 100       213 join '',
    100          
32 2 0       25 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 18 fun fuse_fragment(@parts) {
  3         4  
38 3 0 0     14 return $parts[0]
      33        
39             if @parts == 1 && (ref($parts[0]) eq __PACKAGE__ || ref($parts[0]) eq __PACKAGE__ . '::_Fragment');
40 3         16 HTML::Blitz::Builder::_Fragment->new(@parts)
41             }
42              
43 1 50   1 1 104 fun mk_doctype() {
  1         2  
44 1         2 my $code = '';
45 1         13 bless \$code, __PACKAGE__
46             }
47              
48 6 50   6 1 1856 fun mk_comment($content) {
  6 50       16  
  6         12  
  6         9  
49 6 100       269 $content =~ /\A(-?>)/
50             and croak "HTML comment cannot start with '$1': '$content'";
51 4 100       264 $content =~ /(";
54 1         5 bless \$code, __PACKAGE__
55             }
56              
57 2 50   2   6 fun _mk_attr($name, $value) {
  2 50       5  
  2         4  
  2         5  
58 2 50       7 $name =~ m{\A[^\s/>="'<[:cntrl:]]+\z}
59             or croak "Invalid attribute name '$name'";
60 2         5 my $code = " $name";
61 2 50       5 if ($value ne '') {
62 2         5 $code .= '=';
63 2 100       7 if ($value !~ m{[\s"'=<>`]}) {
64 1 0       3 $code .= $value =~ s{([[:cntrl:]&])}{ $1 eq '&' ? '&' : '&#' . ord($1) . ';' }egr;
  0         0  
65             } else {
66 1 50       10 $code .= '"' . $value =~ s{(?![\n\t])([[:cntrl:]&"])}{ $1 eq '"' ? '"' : $1 eq '&' ? '&' : '&#' . ord($1) . ';' }egr . '"';
  1 50       10  
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 9928 fun mk_elem($name, @args) {
  31         81  
  31         42  
97 31 100 100     142 my $attrs = @args && ref($args[0]) eq 'HASH'
98             ? shift @args
99             : {};
100 31 50       164 $name =~ m{\A[A-Za-z][^\s/>[:cntrl:]]*\z}
101             or croak "Invalid tag name '$name'";
102 31         67 (my $lc_name = $name) =~ tr/A-Z/a-z/;
103 31 50       117 @args = map ref($_) eq __PACKAGE__ . '::_Fragment' ? $_->unwrapped : $_, @args;
104 31         112 my $attr_str = join '', map _mk_attr($_, $attrs->{$_}), sort keys %$attrs;
105 31         72 my $html = "<$name$attr_str>";
106 31 100       74 if ($is_void{$lc_name}) {
107 3 50       13 croak "<$name> tag cannot have contents" if @args;
108             } else {
109 28 50 66     126 croak "<$name> tag cannot have child elements" if $is_childless{$lc_name} && grep ref($_) eq __PACKAGE__, @args;
110 28         38 my $contents;
111 28 100       60 if ($lc_name eq 'style') {
    100          
112 4         12 $contents = join '', @args;
113 4 100       184 $contents =~ m{(])}aai
114             and croak "<$name> tag cannot contain '$1'";
115             } elsif ($lc_name eq 'script') {
116 21         47 $contents = join '', @args;
117             SCRIPT_DATA: {
118 21 100       30 $contents =~ m{ ( ) | ( < (/?) script [ \t\r\n\f/>] ) }xaaigc
  11         39  
123             or last SCRIPT_DATA;
124 8 100       23 $1 and redo SCRIPT_DATA;
125 6 50       16 $3 and croak "<$name> tag cannot contain '$2'";
126              
127 6 100       102 $contents =~ m{ (-->) | ] }xaaigc
128             or croak "<$name> tag cannot contain '' or ''";
129 5 100       14 $1 and redo SCRIPT_DATA;
130 2         4 redo SCRIPT_DATA_ESCAPED;
131             }
132             }
133             } else {
134 3         7 $contents = to_html @args;
135             }
136 19         54 $html .= "$contents";
137             }
138 22         96 bless \$html, __PACKAGE__
139             }
140              
141             1
142             __END__