File Coverage

blib/lib/HTML/Tidy.pm
Criterion Covered Total %
statement 117 117 100.0
branch 47 48 97.9
condition 8 8 100.0
subroutine 19 19 100.0
pod 8 8 100.0
total 199 200 99.5


line stmt bran cond sub pod time code
1             package HTML::Tidy;
2              
3 24     24   1366917 use 5.008;
  24         98  
4 24     24   181 use strict;
  24         51  
  24         540  
5 24     24   123 use warnings;
  24         52  
  24         793  
6 24     24   154 use Carp ();
  24         81  
  24         407  
7              
8 24     24   8417 use HTML::Tidy::Message;
  24         81  
  24         1046  
9              
10             =head1 NAME
11              
12             HTML::Tidy - (X)HTML validation in a Perl object
13              
14             =head1 VERSION
15              
16             Version 1.58
17              
18             =cut
19              
20             our $VERSION = '1.58';
21              
22             =head1 SYNOPSIS
23              
24             use HTML::Tidy;
25              
26             my $tidy = HTML::Tidy->new( {config_file => 'path/to/config'} );
27             $tidy->ignore( type => TIDY_WARNING, type => TIDY_INFO );
28             $tidy->parse( "foo.html", $contents_of_foo );
29              
30             for my $message ( $tidy->messages ) {
31             print $message->as_string;
32             }
33              
34             =head1 DESCRIPTION
35              
36             C<HTML::Tidy> is an HTML checker in a handy dandy object. It's meant as
37             a replacement for L<HTML::Lint|HTML::Lint>. If you're currently an L<HTML::Lint|HTML::Lint>
38             user looking to migrate, see the section L</Converting from HTML::Lint>.
39              
40             =head1 EXPORTS
41              
42             Message types C<TIDY_ERROR>, C<TIDY_WARNING> and C<TIDY_INFO>.
43              
44             Everything else is an object method.
45              
46             =cut
47              
48 24     24   181 use base 'Exporter';
  24         53  
  24         2306  
49              
50 24     24   168 use constant TIDY_ERROR => 3;
  24         57  
  24         1714  
51 24     24   145 use constant TIDY_WARNING => 2;
  24         60  
  24         1056  
52 24     24   178 use constant TIDY_INFO => 1;
  24         58  
  24         26843  
53              
54             our @EXPORT = qw( TIDY_ERROR TIDY_WARNING TIDY_INFO );
55              
56             =head1 METHODS
57              
58             =head2 new()
59              
60             Create an HTML::Tidy object.
61              
62             my $tidy = HTML::Tidy->new();
63              
64             Optionally you can give a hashref of configuration parms.
65              
66             my $tidy = HTML::Tidy->new( {config_file => 'path/to/tidy.cfg'} );
67              
68             This configuration file will be read and used when you clean or parse an HTML file.
69              
70             You can also pass options directly to tidyp.
71              
72             my $tidy = HTML::Tidy->new( {
73             output_xhtml => 1,
74             tidy_mark => 0,
75             } );
76              
77             See C<tidyp -help-config> for the list of options supported by tidyp.
78              
79             The following options are not supported by C<HTML::Tidy>:
80              
81             =over 4
82              
83             =item * quiet
84              
85             =back
86              
87             =cut
88              
89             sub new {
90 30     30 1 9372 my $class = shift;
91 30   100     170 my $args = shift || {};
92 30         160 my @unsupported_options = qw(
93             force-output
94             gnu-emacs-file
95             gnu-emacs
96             keep-time
97             quiet
98             slide-style
99             write-back
100             ); # REVIEW perhaps a list of supported options would be better
101              
102 30         173 my $self = bless {
103             messages => [],
104             ignore_type => [],
105             ignore_text => [],
106             config_file => '',
107             tidy_options => {},
108             }, $class;
109              
110 30         75 for my $key (keys %{$args} ) {
  30         122  
111 34 100       93 if ($key eq 'config_file') {
112 7         28 $self->{config_file} = $args->{$key};
113 7         18 next;
114             }
115              
116 27         44 my $newkey = $key;
117 27         51 $newkey =~ tr/_/-/;
118              
119 27 100       53 if ( grep {$newkey eq $_} @unsupported_options ) {
  189         333  
120 7         78 Carp::croak( "Unsupported option: $newkey" );
121             }
122              
123 20         63 $self->{tidy_options}->{$newkey} = $args->{$key};
124             }
125              
126 23         91 return $self;
127             }
128              
129             =head2 messages()
130              
131             Returns the messages accumulated.
132              
133             =cut
134              
135             sub messages {
136 14     14 1 5736 my $self = shift;
137              
138 14         39 return @{$self->{messages}};
  14         83  
139             }
140              
141             =head2 clear_messages()
142              
143             Clears the list of messages, in case you want to print and clear, print
144             and clear. If you don't clear the messages, then each time you call
145             L<parse()|parse( $filename, $str [, $str...] )> you'll be accumulating more in the list.
146              
147             =cut
148              
149             sub clear_messages {
150 1     1 1 734 my $self = shift;
151              
152 1         3 $self->{messages} = [];
153              
154 1         3 return;
155             }
156              
157             =head2 ignore( parm => value [, parm => value ] )
158              
159             Specify types of messages to ignore. Note that the ignore flags must be
160             set B<before> calling C<parse()>. You can call C<ignore()> as many times
161             as necessary to set up all your restrictions; the options will stack up.
162              
163             =over 4
164              
165             =item * type => TIDY_INFO|TIDY_WARNING|TIDY_ERROR
166              
167             Specifies the type of messages you want to ignore, either info or warnings
168             or errors. If you wanted, you could call ignore on all three and get
169             no messages at all.
170              
171             $tidy->ignore( type => TIDY_WARNING );
172              
173             =item * text => qr/regex/
174              
175             =item * text => [ qr/regex1/, qr/regex2/, ... ]
176              
177             Checks the text of the message against the specified regex or regexes,
178             and ignores the message if there's a match. The value for the I<text>
179             parm may be either a regex, or a reference to a list of regexes.
180              
181             $tidy->ignore( text => qr/DOCTYPE/ );
182             $tidy->ignore( text => [ qr/unsupported/, qr/proprietary/i ] );
183              
184             =back
185              
186             =cut
187              
188             sub ignore {
189 14     14 1 7396 my $self = shift;
190 14         46 my @parms = @_;
191              
192 14         54 while ( @parms ) {
193 14         29 my $parm = shift @parms;
194 14         33 my $value = shift @parms;
195 14 100       55 my @values = ref($value) eq 'ARRAY' ? @{$value} : ($value);
  1         2  
196              
197 14 100 100     221 Carp::croak( qq{Invalid ignore type of "$parm"} )
198             unless ($parm eq 'text') or ($parm eq 'type');
199              
200 13         45 push( @{$self->{"ignore_$parm"}}, @values );
  13         87  
201             } # while
202              
203 13         35 return;
204             } # ignore
205              
206             =head2 parse( $filename, $str [, $str...] )
207              
208             Parses a string, or list of strings, that make up a single HTML file.
209              
210             The I<$filename> parm is only used as an identifier for your use.
211             The file is not actually read and opened.
212              
213             Returns true if all went OK, or false if there was some problem calling
214             tidy, or parsing tidy's output.
215              
216             =cut
217              
218             sub parse {
219 11     11 1 3133 my $self = shift;
220 11         29 my $filename = shift;
221 11 100       69 if (@_ == 0) {
222 1         23 Carp::croak('Usage: parse($filename,$str [, $str...])') ## no critic
223             }
224 10         127 my $html = join( '', @_ );
225              
226 10 100       52 utf8::encode($html) if utf8::is_utf8($html);
227 10         5076 my ($errorblock,$newline) = _tidy_messages( $html, $self->{config_file}, $self->{tidy_options} );
228 10 100       70 return 1 unless defined $errorblock;
229              
230 8         39 utf8::decode($errorblock);
231              
232 8         34 return !$self->_parse_errors($filename, $errorblock, $newline);
233             }
234              
235             sub _parse_errors {
236 22     22   122 my $self = shift;
237 22         62 my $filename = shift;
238 22         58 my $errs = shift;
239 22         70 my $newline = shift;
240              
241 22         58 my $parse_errors;
242              
243 22         359 my @lines = split( /$newline/, $errs );
244              
245 22         86 for my $line ( @lines ) {
246 196         440 chomp $line;
247              
248 196         320 my $message;
249 196 100       1130 if ( $line =~ /^line (\d+) column (\d+) - (Warning|Error|Info): (.+)$/ ) {
    100          
    100          
    100          
    100          
    100          
    100          
250 159         661 my ($line, $col, $type, $text) = ($1, $2, $3, $4);
251 159 100       464 $type =
    100          
252             ($type eq 'Warning') ? TIDY_WARNING :
253             ($type eq 'Info') ? TIDY_INFO :
254             TIDY_ERROR;
255 159         554 $message = HTML::Tidy::Message->new( $filename, $type, $line, $col, $text );
256              
257             }
258             elsif ( $line =~ m/^Info: (.+)$/ ) {
259             # Info line we don't want
260              
261 16         43 my $text = $1;
262 16         60 $message = HTML::Tidy::Message->new( $filename, TIDY_INFO, undef, undef, $text );
263             }
264             elsif ( $line =~ /^\d+ warnings?, \d+ errors? were found!/ ) {
265             # Summary line we don't want
266              
267             }
268             elsif ( $line eq 'No warnings or errors were found.' ) {
269             # Summary line we don't want
270              
271             }
272             elsif ( $line eq 'This document has errors that must be fixed before' ) {
273             # Summary line we don't want
274              
275             }
276             elsif ( $line eq 'using HTML Tidy to generate a tidied up version.' ) {
277             # Summary line we don't want
278              
279             }
280             elsif ( $line =~ m/^\s*$/ ) {
281             # Blank line we don't want
282              
283             }
284             else {
285 1         26 Carp::carp "HTML::Tidy: Unknown error type: $line";
286 1         427 ++$parse_errors;
287             }
288 196 100 100     902 push( @{$self->{messages}}, $message )
  155         453  
289             if $message && $self->_is_keeper( $message );
290             } # for
291 22         82 return $parse_errors;
292             }
293              
294             =head2 clean( $str [, $str...] )
295              
296             Cleans a string, or list of strings, that make up a single HTML file.
297              
298             Returns the cleaned string as a single string.
299              
300             =cut
301              
302             sub clean {
303 14     14 1 8066 my $self = shift;
304 14 100       54 if (@_ == 0) {
305 1         13 Carp::croak('Usage: clean($str [, $str...])') ## no critic
306             }
307 13         49 my $text = join( '', @_ );
308              
309 13 100       77 utf8::encode($text) if utf8::is_utf8($text);
310 13 50       46 if ( defined $text ) {
311 13         31 $text .= "\n";
312             }
313              
314             my ($cleaned, $errbuf, $newline) = _tidy_clean( $text,
315             $self->{config_file},
316 13         4014 $self->{tidy_options});
317 13         114 utf8::decode($cleaned);
318 13         49 utf8::decode($errbuf);
319              
320 13         60 $self->_parse_errors('', $errbuf, $newline);
321 13         74 return $cleaned;
322             }
323              
324             # Tells whether a given message object is one that we should keep.
325              
326             sub _is_keeper {
327 175     175   447 my $self = shift;
328              
329 175         258 my $message = shift;
330              
331 175         246 my @ignore_types = @{$self->{ignore_type}};
  175         383  
332 175 100       429 if ( @ignore_types ) {
333 41 100       78 return if grep { $message->type == $_ } @ignore_types;
  41         103  
334             }
335              
336 160         246 my @ignore_texts = @{$self->{ignore_text}};
  160         282  
337 160 100       348 if ( @ignore_texts ) {
338 13 100       21 return if grep { $message->text =~ $_ } @ignore_texts;
  23         57  
339             }
340              
341 155         523 return 1;
342             }
343              
344             =head2 tidyp_version()
345              
346             =head2 libtidyp_version()
347              
348             Returns the version of the underling tidyp library.
349              
350             =cut
351              
352             # backcompat
353 1     1 1 4 sub libtidyp_version { return shift->tidyp_version }
354              
355             sub tidyp_version {
356 3     3 1 224 my $version_str = _tidyp_version();
357              
358 3         19 return $version_str;
359             }
360              
361             require XSLoader;
362             XSLoader::load('HTML::Tidy', $VERSION);
363              
364             1;
365              
366             __END__
367              
368             =head1 INSTALLING TIDYP
369              
370             C<HTML::Tidy> requires that C<tidyp> be installed on your system.
371             You can obtain tidyp through your distribution's package manager
372             (make sure you install the development package with headers), or from
373             the tidyp Git repository at L<http://github.com/petdance/tidyp>.
374              
375             =head1 CONVERTING FROM C<HTML::Lint>
376              
377             C<HTML::Tidy> is different from C<HTML::Lint> in a number of crucial ways.
378              
379             =over 4
380              
381             =item * It's not pure Perl
382              
383             C<HTML::Tidy> is mostly a happy wrapper around tidyp.
384              
385             =item * The real work is done by someone else
386              
387             Changes to tidyp may come down the pipe that I don't have control over.
388             That's the price we pay for having it do a darn good job.
389              
390             =item * It's no longer bundled with its C<Test::> counterpart
391              
392             L<HTML::Lint|HTML::Lint> came bundled with C<Test::HTML::Lint>, but
393             L<Test::HTML::Tidy|Test::HTML::Tidy> is a separate distribution. This saves the people
394             who don't want the C<Test::> framework from pulling it in, and all its
395             prerequisite modules.
396              
397             =back
398              
399             =head1 BUGS & FEEDBACK
400              
401             Please report any bugs or feature requests at the issue tracker on github
402             L<http://github.com/petdance/html-tidy/issues>. I will be notified,
403             and then you'll automatically be notified of progress on your bug as I
404             make changes.
405              
406             Please do NOT use L<http://rt.cpan.org>.
407              
408             =head1 SUPPORT
409              
410             You can find documentation for this module with the perldoc command.
411              
412             perldoc HTML::Tidy
413              
414             You can also look for information at:
415              
416             =over 4
417              
418             =item * HTML::Tidy's issue queue at github
419              
420             L<http://github.com/petdance/html-tidy/issues>
421              
422             =item * AnnoCPAN: Annotated CPAN documentation
423              
424             L<http://annocpan.org/dist/HTML-Tidy>
425              
426             =item * CPAN Ratings
427              
428             L<http://cpanratings.perl.org/d/HTML-Tidy>
429              
430             =item * search.cpan.org
431              
432             L<http://search.cpan.org/dist/HTML-Tidy>
433              
434             =item * Git source code repository
435              
436             L<http://github.com/petdance/html-tidy>
437              
438             =back
439              
440             =head1 ACKNOWLEDGEMENTS
441              
442             Thanks to Jonathan Rockway and Robert Bachmann for contributions.
443              
444             =head1 AUTHOR
445              
446             Andy Lester, C<< <andy at petdance.com> >>
447              
448             =head1 COPYRIGHT & LICENSE
449              
450             Copyright (C) 2005-2017 by Andy Lester
451              
452             This library is free software. You mean modify or distribute it under
453             the Artistic License v2.0.
454              
455             =cut