File Coverage

blib/lib/Plack/Middleware/HTMLLint.pm
Criterion Covered Total %
statement 89 90 98.8
branch 22 26 84.6
condition 4 5 80.0
subroutine 15 15 100.0
pod 2 3 66.6
total 132 139 94.9


line stmt bran cond sub pod time code
1             package Plack::Middleware::HTMLLint;
2 2     2   90656 use 5.008_001;
  2         8  
  2         93  
3 2     2   12 use strict;
  2         5  
  2         75  
4 2     2   14 use warnings;
  2         9  
  2         166  
5              
6             our $VERSION = '0.02';
7              
8 2     2   1085 use parent qw/ Plack::Middleware /;
  2         376  
  2         15  
9              
10             use constant +{
11 2         216 PSGI_STATUS => 0,
12             PSGI_HEADER => 1,
13             PSGI_BODY => 2,
14 2     2   23652 };
  2         13  
15              
16             use constant +{
17 2         129 SYNTAX_HTML5 => 'html5',
18             SYNTAX_HTML4 => 'html4',
19             SYNTAX_XHTML => 'xhtml',
20 2     2   11 };
  2         5  
21              
22 2     2   11 use Plack::Util;
  2         3  
  2         46  
23 2     2   18 use Plack::Util::Accessor qw/error2html/;
  2         4  
  2         13  
24 2     2   1898 use HTML::Lint;
  2         109225  
  2         77  
25 2     2   2125 use HTML::Escape qw/escape_html/;
  2         1953  
  2         1965  
26              
27             sub prepare_app {
28 2     2 1 665 my $self = shift;
29 2 50       8 unless ($self->error2html) {
30             $self->error2html(sub {
31 4     4   33 my @errors = @_;
32              
33 4         32 my @error_html;
34 4         8 push @error_html => '
';
35 4         9 push @error_html => '

HTML Error

';
36 4         8 push @error_html => '
';
37 4         9 foreach my $error (@errors) {
38 6         96 push @error_html => '
', escape_html($error->errcode), '
';
39 6         68 push @error_html => '
', escape_html($error->as_string), '
';
40             }
41 4         127 push @error_html => '';
42              
43 4         9 push @error_html => '';
44              
45 4         33 return join '', @error_html;
46 2         101 });
47             }
48             }
49              
50             sub call {
51 8     8 1 49207 my($self, $env) = @_;
52              
53             return $self->response_cb($self->app->($env), sub {
54 8     8   904 my $res = shift;
55 8   50     29 my $content_type = Plack::Util::header_get($res->[PSGI_HEADER], 'Content-Type') || '';
56              
57 8 100       226 if ($content_type =~ m{^(?:text/x?html|application/xhtml\+xml)\b}io) {# HTML/XHTML
58             my $do_lint = sub {
59 6         11 my $content = shift;
60              
61 6 50       30 my $syntax = ($content =~ /^$/imo) ? SYNTAX_HTML5:
    50          
62             ($content_type =~ m{^(?:text/xhtml|application/xhtml\+xml)\b}io) ? SYNTAX_XHTML:
63             SYNTAX_HTML4;
64              
65 6 100       18 if (my @errors = $self->html_lint($syntax => $content)) {
66 4         58 return $self->error2html->(@errors);
67             }
68             else {
69 2         38 return '';
70             }
71 6         26 };
72              
73 6 100       17 if ($res->[PSGI_BODY]) {
74 3         4 my $content = '';
75 3         16 Plack::Util::foreach($res->[PSGI_BODY] => sub { $content .= $_[0] });
  3         27  
76 3 100       16 if (my $error_html = $do_lint->($content)) {
77 2 100       20 unless ($content =~ s{]*)>}{$error_html}i) {
78             ## fallback
79 1         3 $content .= $error_html;
80             }
81 2         15 $res->[PSGI_BODY] = [$content];
82             }
83             }
84             else {
85             # XXX: It has become increasingly complex not to block the stream as possible.
86 3         9 my $buffer = '';
87 3         4 my $html_last_buffer = '';
88 3         5 my $end_of_html_body = 0;
89 3         3 my $do_lint_finished = 0;
90             return sub {
91 33         1221 my $body_chunk = shift;
92 33 100       58 if (defined $body_chunk) {
93 30         37 $buffer .= $body_chunk;
94 30 100 100     116 if ($end_of_html_body || $body_chunk =~ m{}io) {
95 8         11 $end_of_html_body = 1;
96 8         9 $html_last_buffer .= $body_chunk;
97 8         36 return '';
98             }
99             else {
100 22         101 return $body_chunk;
101             }
102             }
103             else {
104 3 50       9 if ($do_lint_finished) {
105 0         0 return;
106             }
107             else {
108 3         7 my $error_html = $do_lint->($buffer);
109 3 100       15 if ($error_html) {
110 2 100       15 unless ($html_last_buffer =~ s{}{$error_html}i) {
111             ## fallback
112 1         4 $html_last_buffer = $error_html . $html_last_buffer;
113             }
114             }
115              
116 3         5 $do_lint_finished = 1;
117 3         11 return $html_last_buffer;
118             }
119             }
120 3         16 };
121             }
122             }
123              
124 5         28 return;
125 8         41 });
126             }
127              
128             sub html_lint {
129 6     6 0 9 my($self, $syntax, $content) = @_;
130              
131 6         41 my $lint = HTML::Lint->new;
132 6         104 $lint->parse($content);
133 6         9835 $lint->eof;
134              
135 6         595 return $lint->errors;
136             }
137              
138             1;
139             __END__