File Coverage

blib/lib/WWW/DoctypeGrabber.pm
Criterion Covered Total %
statement 63 80 78.7
branch 15 36 41.6
condition 3 7 42.8
subroutine 11 12 91.6
pod 2 2 100.0
total 94 137 68.6


line stmt bran cond sub pod time code
1             package WWW::DoctypeGrabber;
2              
3 1     1   115442 use warnings;
  1         5  
  1         46  
4 1     1   9 use strict;
  1         2  
  1         112  
5              
6             our $VERSION = '0.007';
7              
8 1     1   9 use Carp;
  1         8  
  1         114  
9 1     1   8 use LWP::UserAgent;
  1         3  
  1         31  
10 1     1   7 use base 'Class::Accessor::Grouped';
  1         2  
  1         200  
11 1     1   6 use overload q|""|, sub { shift->doctype; };
  1     1   2  
  1         23  
  1         1831  
12             __PACKAGE__->mk_group_accessors( simple => qw(
13             ua
14             error
15             doctype
16             result
17             raw
18             ));
19              
20             sub new {
21 1     1 1 201 my $self = bless {}, shift;
22 1 50       4 croak "Must have even number of arguments to new()"
23             if @_ & 1;
24              
25 1         2 my %args = @_;
26 1         4 $args{ +lc } = delete $args{ $_ } for keys %args;
27 1   50     8 $args{timeout} ||= 30;
28 1   50     4 $args{max_size} ||= 500;
29 1   33     15 $args{ua} ||= LWP::UserAgent->new(
30             timeout => 30,
31             max_size => 500,
32             agent => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1.13)'
33             . ' Gecko/20080325 Ubuntu/7.10 (gutsy) Firefox/2.0.0.13',
34             );
35              
36 1         3467 $self->ua( $args{ua} );
37 1         320 $self->raw( $args{raw} );
38 1         172 return $self;
39             }
40              
41             sub grab {
42 1     1 1 1106 my ( $self, $uri ) = @_;
43              
44 1         6 $self->$_(undef) for qw(error doctype result);
45              
46 1 50       425 $uri = "http://$uri"
47             unless $uri =~ m{^https?://};
48              
49 1         7 my $response = $self->ua->get( $uri );
50 1 50       471288 if ( $response->is_success ) {
51 1         21 return $self->result(
52             $self->_parse_doctype(
53             $response->content,
54             $response->header('Content-type'),
55             )
56             );
57             }
58             else {
59 0         0 return $self->_set_error( $response, 'net' );
60             }
61             }
62              
63             sub _parse_doctype {
64 1     1   49 my ( $self, $content, $mime ) = @_;
65              
66 1         8 my %parse_result = (
67             xml_prolog => 0,
68             non_white_space => 0,
69             has_doctype => 0,
70             doctype => '',
71             mime => $mime,
72             );
73              
74             DOCTYPE_PARSE: {
75 1 50       4 if ( my ( $pre_text ) = $content =~ /(.+)(?=
  1         4242  
76 0 0       0 if ( my $xml_count = $pre_text =~ s/<\?xml[^>]+?\?>//ig ) {
77 0         0 $parse_result{xml_prolog} = $xml_count;
78             }
79 0         0 $pre_text =~ s/\s+//g;
80 0         0 $parse_result{non_white_space} = length $pre_text;
81             }
82              
83 1 50       14 if ( my ( $doctype_string ) = $content =~ m{(]+>)}i ) {
    0          
84 1         3 $parse_result{has_doctype} = 1;
85 1         10 $doctype_string =~ s/\s+/ /g;
86 1         18 $doctype_string =~ s/^\s+|\s+$//g;
87 1 50       9 $self->raw
88             and return $self->doctype($doctype_string);
89              
90 1 50       6 if ( $doctype_string
91             =~ s{^
92             ) {
93 1         2 my @doctype_bits = ();
94 1         7 my ( $type ) = $doctype_string =~ m{^[^/]+?(?=//)}g;
95 1 50       9 if ( !defined $type ) {
96 0         0 $parse_result{doctype} = 'Invalid/Unknown';
97              
98 0         0 last DOCTYPE_PARSE;
99             }
100 1 50       6 $type =~ /^HTML 4.01$/i
101             and $type .= ' Strict';
102              
103 1         3 push @doctype_bits, $type;
104 1 50       9 if ( my ( $dtd_uri ) =
105             $doctype_string =~ m{\s"(\S+)"\s*>$}
106             ) {
107 1         6 my $dtd_uris = $self->_get_dtd_uri_table;
108 1 50       4 if ( exists $dtd_uris->{ $type } ) {
109 1 50       4 if ( $dtd_uris->{ $type } eq $dtd_uri ) {
110 1         4 push @doctype_bits, '+ url';
111             }
112             else {
113 0         0 push @doctype_bits, '+ Invalid url';
114             }
115             }
116             else {
117 0         0 push @doctype_bits, '+ Unknown url';
118             }
119             }
120 1         7 $parse_result{doctype} = join q| |, @doctype_bits;
121             }
122             else {
123 0         0 $parse_result{doctype} = $doctype_string;
124             }
125             }
126             elsif( $self->raw ) {
127 0         0 return $self->doctype('NO DOCTYPE');
128             }
129             }
130              
131 1 50       4 if ( $parse_result{has_doctype} ) {
132 1         3 my @bits;
133 1 50       4 $parse_result{xml_prolog}
134             and push @bits, "+ $parse_result{xml_prolog} XML prolog";
135              
136 1 50       3 $parse_result{non_white_space}
137             and push @bits, "+ $parse_result{non_white_space} "
138             . "non-whitespace characters";
139              
140 1         23 $self->doctype( join q| |, $parse_result{doctype}, @bits );
141             }
142             else {
143 0         0 $self->doctype('NO DOCTYPE');
144             }
145              
146 1         25 return \%parse_result;
147             }
148              
149             sub _get_dtd_uri_table {
150             return {
151 1     1   14 'HTML 4.01 Strict' =>
152             'http://www.w3.org/TR/html4/strict.dtd',
153              
154             'HTML 4.01 Transitional' =>
155             'http://www.w3.org/TR/html4/loose.dtd',
156              
157             'HTML 4.01 Frameset' =>
158             'http://www.w3.org/TR/html4/frameset.dtd',
159              
160             'XHTML 1.0 Strict' =>
161             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd',
162              
163             'XHTML 1.0 Transitional' =>
164             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd',
165              
166             'XHTML 1.0 Frameset' =>
167             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd',
168              
169             'XHTML 1.1' =>
170             'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd',
171              
172             'XHTML Basic 1.0' =>
173             'http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd',
174              
175             'XHTML Basic 1.1' =>
176             'http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd',
177              
178             'XHTML 1.1 plus MathML 2.0 plus SVG 1.1' =>
179             'http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd',
180             };
181             }
182              
183             sub _set_error {
184 0     0     my ( $self, $error_or_resp, $is_net ) = @_;
185 0 0         if ( $is_net ) {
186 0           $self->error( 'Network error: ' . $error_or_resp->status_line );
187             }
188             else {
189 0           $self->error( $error_or_resp );
190             }
191 0           return;
192             }
193              
194             1;
195             __END__