File Coverage

blib/lib/HTML/Tidy.pm
Criterion Covered Total %
statement 112 117 95.7
branch 43 48 89.5
condition 8 8 100.0
subroutine 19 19 100.0
pod 8 8 100.0
total 190 200 95.0


line stmt bran cond sub pod time code
1             package HTML::Tidy;
2              
3 20     20   929176 use 5.008;
  20         47  
4 20     20   76 use strict;
  20         23  
  20         319  
5 20     20   62 use warnings;
  20         27  
  20         382  
6 20     20   62 use Carp ();
  20         27  
  20         284  
7              
8 20     20   6392 use HTML::Tidy::Message;
  20         37  
  20         693  
9              
10             =head1 NAME
11              
12             HTML::Tidy - (X)HTML validation in a Perl object
13              
14             =head1 VERSION
15              
16             Version 1.56
17              
18             =cut
19              
20             our $VERSION = '1.56';
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 20     20   89 use base 'Exporter';
  20         24  
  20         1620  
49              
50 20     20   76 use constant TIDY_ERROR => 3;
  20         19  
  20         1240  
51 20     20   64 use constant TIDY_WARNING => 2;
  20         19  
  20         651  
52 20     20   68 use constant TIDY_INFO => 1;
  20         29  
  20         17840  
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 20     20 1 2625 my $class = shift;
91 20   100     89 my $args = shift || {};
92 20         66 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 20         89 my $self = bless {
103             messages => [],
104             ignore_type => [],
105             ignore_text => [],
106             config_file => '',
107             tidy_options => {},
108             }, $class;
109              
110 20         34 for my $key (keys %{$args} ) {
  20         61  
111 23 100       86 if ($key eq 'config_file') {
112 3         19 $self->{config_file} = $args->{$key};
113 3         5 next;
114             }
115              
116 20         17 my $newkey = $key;
117 20         27 $newkey =~ tr/_/-/;
118              
119 20 50       17 if ( grep {$newkey eq $_} @unsupported_options ) {
  140         135  
120 0         0 Carp::croak( "Unsupported option: $newkey" );
121             }
122              
123 20         49 $self->{tidy_options}->{$newkey} = $args->{$key};
124             }
125              
126 20         53 return $self;
127             }
128              
129             =head2 messages()
130              
131             Returns the messages accumulated.
132              
133             =cut
134              
135             sub messages {
136 14     14 1 2879 my $self = shift;
137              
138 14         17 return @{$self->{messages}};
  14         41  
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 352 my $self = shift;
151              
152 1         1 $self->{messages} = [];
153              
154 1         2 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 4512 my $self = shift;
190 14         27 my @parms = @_;
191              
192 14         60 while ( @parms ) {
193 14         22 my $parm = shift @parms;
194 14         40 my $value = shift @parms;
195 14 100       41 my @values = ref($value) eq 'ARRAY' ? @{$value} : ($value);
  1         12  
196              
197 14 100 100     229 Carp::croak( qq{Invalid ignore type of "$parm"} )
198             unless ($parm eq 'text') or ($parm eq 'type');
199              
200 13         16 push( @{$self->{"ignore_$parm"}}, @values );
  13         68  
201             } # while
202              
203 13         21 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 10     10 1 1489 my $self = shift;
220 10         16 my $filename = shift;
221 10 50       33 if (@_ == 0) {
222 0         0 Carp::croak('Usage: parser($filename,$str [, $str...])') ## no critic
223             }
224 10         36 my $html = join( '', @_ );
225              
226 10 100       38 utf8::encode($html) if utf8::is_utf8($html);
227 10         4371 my ($errorblock,$newline) = _tidy_messages( $html, $self->{config_file}, $self->{tidy_options} );
228 10 100       43 return 1 unless defined $errorblock;
229              
230 8         40 utf8::decode($errorblock);
231              
232 8         23 return !$self->_parse_errors($filename, $errorblock, $newline);
233             }
234              
235             sub _parse_errors {
236 19     19   39 my $self = shift;
237 19         37 my $filename = shift;
238 19         25 my $errs = shift;
239 19         24 my $newline = shift;
240              
241 19         17 my $parse_errors;
242              
243 19         254 my @lines = split( /$newline/, $errs );
244              
245 19         43 for my $line ( @lines ) {
246 180         183 chomp $line;
247              
248 180         122 my $message;
249 180 100       768 if ( $line =~ /^line (\d+) column (\d+) - (Warning|Error|Info): (.+)$/ ) {
    100          
    100          
    100          
    100          
    100          
    50          
250 151         349 my ($line, $col, $type, $text) = ($1, $2, $3, $4);
251 151 100       277 $type =
    100          
252             ($type eq 'Warning') ? TIDY_WARNING :
253             ($type eq 'Info') ? TIDY_INFO :
254             TIDY_ERROR;
255 151         335 $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 13         25 my $text = $1;
262 13         40 $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 0         0 Carp::carp "HTML::Tidy: Unknown error type: $line";
286 0         0 ++$parse_errors;
287             }
288 180 100 100     950 push( @{$self->{messages}}, $message )
  144         241  
289             if $message && $self->_is_keeper( $message );
290             } # for
291 19         52 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 11     11 1 2909 my $self = shift;
304 11 50       34 if (@_ == 0) {
305 0         0 Carp::croak('Usage: clean($str [, $str...])') ## no critic
306             }
307 11         35 my $text = join( '', @_ );
308              
309 11 100       41 utf8::encode($text) if utf8::is_utf8($text);
310 11 50       49 if ( defined $text ) {
311 11         18 $text .= "\n";
312             }
313              
314             my ($cleaned, $errbuf, $newline) = _tidy_clean( $text,
315             $self->{config_file},
316 11         3523 $self->{tidy_options});
317 11         77 utf8::decode($cleaned);
318 11         31 utf8::decode($errbuf);
319              
320 11         35 $self->_parse_errors('', $errbuf, $newline);
321 11         31 return $cleaned;
322             }
323              
324             # Tells whether a given message object is one that we should keep.
325              
326             sub _is_keeper {
327 164     164   123 my $self = shift;
328              
329 164         106 my $message = shift;
330              
331 164         105 my @ignore_types = @{$self->{ignore_type}};
  164         199  
332 164 100       257 if ( @ignore_types ) {
333 41 100       43 return if grep { $message->type == $_ } @ignore_types;
  41         66  
334             }
335              
336 149         103 my @ignore_texts = @{$self->{ignore_text}};
  149         140  
337 149 100       190 if ( @ignore_texts ) {
338 13 100       13 return if grep { $message->text =~ $_ } @ignore_texts;
  23         29  
339             }
340              
341 144         356 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 2 sub libtidyp_version { return shift->tidyp_version }
354              
355             sub tidyp_version {
356 3     3 1 235 my $version_str = _tidyp_version();
357              
358 3         17 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-2013 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