File Coverage

blib/lib/WWW/Mechanize/Query.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1 1     1   23701 use strict;
  1         2  
  1         30  
2 1     1   5 use warnings;
  1         1  
  1         56  
3              
4             package WWW::Mechanize::Query;
5              
6             =head1 NAME
7              
8             WWW::Mechanize::Query - CSS3 selectors (or jQuery like CSS selectors) for WWW::Mechanize.
9              
10             =head1 VERSION
11              
12             Version 0.03
13              
14             =cut
15              
16             our $VERSION = '0.03';
17              
18             =head1 SYNOPSIS
19              
20             use WWW::Mechanize::Query;
21              
22             my $mech = WWW::Mechanize::Query->new( -ignore_cache => 0, -debug => 0 );
23             $mech->get( 'http://www.amazon.com/' );
24             $mech->input( 'input[type="text"][name="field-keywords"]', 'Perl' );
25             $mech->submit();
26              
27             print $mech->at('h2.resultCount')->span->text; #prints "Showing 1 - 16 of 7,104 Results"
28              
29             =head1 DESCRIPTION
30              
31             This module combines L<WWW::Mechanize> with L<Mojo::DOM> making it possible to fill forms and scrape web with help of CSS3 selectors.
32              
33             For a full list of supported CSS selectors please see L<Mojo::DOM::CSS>.
34              
35             =cut
36              
37 1     1   793 use parent qw(WWW::Mechanize::Cached);
  1         261  
  1         4  
38             use Data::Dumper;
39             use Mojo::DOM;
40             use Regexp::Common qw /URI/;
41              
42             =head1 CONSTRUCTOR
43              
44             =head2 new
45              
46             Creates a new L<WWW::Mechanize>'s C<new> object with any passed arguments.
47              
48             WWW::Mechanize::Query also adds simple request caching (unless I<ignore_cache> is set to true). Also sets I<Firefox> as the default user-agent (if not explicitly specified).
49              
50             my $mech = WWW::Mechanize::Query->new( ignore_cache => 0, agent => 'LWP' );
51              
52             =cut
53              
54             sub new {
55             my $class = shift;
56             my %mech_args = @_;
57              
58             if ( !$mech_args{agent} ) {
59             $mech_args{agent} = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko/20100101 Firefox/11.0';
60             }
61              
62             my $self = $class->SUPER::new( %mech_args );
63              
64             if ( $mech_args{'-ignore_cache'} ) {
65             $self->{ignore_cache} = 1;
66             }
67            
68             $self->{'_internal'}->{'mojo'} = Mojo::DOM->new();
69             $self->cookie_jar->{ignore_discard} = 1;
70              
71             return $self;
72             }
73              
74             sub _make_request {
75             my $self = shift;
76             my $request = shift;
77             my $response = undef;
78             my $cache = !$self->{ignore_cache};
79             my $log = '';
80              
81             unless ( $self->{debug} ) {
82             my $str = "" . $request->as_string;
83             my $uri = $str =~ m[(http.*)] ? $1 : $str;
84             $log .= "Mech Debug: " . $uri;
85             }
86              
87             if ( !$cache ) {
88             my $req = $request;
89              
90             if ( !$self->ref_in_cache_key ) {
91             my $clone = $request->clone;
92             $clone->header( Referer => undef );
93             $req = $clone->as_string;
94             }
95              
96             $self->cache->remove( $req );
97             }
98              
99             $response = $self->SUPER::_make_request( $request, @_ );
100              
101             unless ( $self->{debug} ) {
102             $log .= " (cached: " . ( $self->is_cached() ? 1 : 0 ) . ", status: " . $response->code . ")\n";
103             open( SAV, ">>c:/mechanize.log" ) and print( SAV $log ) and close( SAV );
104              
105             if ( $self->{'-debug'} ) {
106             print $log;
107             }
108             }
109              
110             return $response;
111             } ## end sub _make_request
112              
113             =head1 METHODS
114              
115             Methods provided by L<WWW::Mechanize> can be accessed directly.
116              
117             Methods provided by L<Mojo::DOM> are accessible by calling I<dom()> method.
118              
119             =head2 dom()
120              
121             Parses the current content and returns a L<Mojo::DOM> object.
122              
123             my $dom = $mech->dom;
124             print $dom->to_xml();
125              
126             =cut
127              
128             sub dom {
129             my $self = shift;
130             my $content = $self->content;
131              
132             if ( !$self->{'_internal'}->{'_last_content'} || ( $content ne $self->{'_internal'}->{'_last_content'} ) || !$self->{'_internal'}->{'_last_dom'} ) {
133             $self->{'_internal'}->{'_last_content'} = $content;
134             $self->{'_internal'}->{'_last_dom'} = $self->{'_internal'}->{'mojo'}->parse( $content );
135             }
136              
137             return $self->{'_internal'}->{'_last_dom'};
138             }
139              
140             =head2 at()
141              
142             Parses the current content and returns a L<Mojo::DOM> object using CSS3 selectors.
143              
144             my $mech = WWW::Mechanize::Query->new();
145             $mech->get( 'http://www.amazon.com/' );
146             print $mech->at( 'div > h2' )->text;
147              
148             =cut
149              
150             sub at {
151             my $self = shift;
152             my $expr = shift;
153              
154             return $self->dom->at( $expr );
155             }
156              
157             =head2 find()
158              
159             Parses the current content and returns a L<Mojo::DOM> collection using CSS3 selectors.
160              
161             my $mech = WWW::Mechanize::Query->new();
162             $mech->get( 'http://www.amazon.com/' );
163             print $mech->find( 'div > h2' )->each ( sub { print shift->all_text; } );
164              
165             =cut
166              
167             sub find {
168             my $self = shift;
169             my $expr = shift;
170              
171             return $self->dom->find( $expr );
172             }
173              
174             =head2 input()
175              
176             Gets or sets Form fields using CSS3 selectors.
177              
178             my $mech = WWW::Mechanize::Query->new();
179             $mech->get( 'http://www.imdb.com/' );
180             $mech->input( 'input[name="q"]', 'lost' ); #fill name
181             $mech->input( 'select[name="s"]', 'ep' ); #select "TV" from drop-down list
182             $mech->submit();
183              
184             print $mech->content;
185             print $mech->input( 'input[name="q"]' ); #prints "lost";
186              
187             #TODO: Right now it fills out the first matching field but should be restricted to selected form.
188              
189             =cut
190              
191             sub input {
192             my $self = shift;
193             my $ele = shift;
194             my $value = shift;
195             my $getter = !defined( $value );
196             my $o = $ele;
197              
198             if ( ref( $ele ) ne 'Mojo::DOM' ) {
199             $ele = $self->at( $ele );
200             }
201              
202             die "No '$o' exists" unless $ele;
203             die "Not supported" unless ( $ele->type =~ /input|select|textarea/i );
204              
205             my $dom = $self->dom;
206              
207             if ( ( $ele->type =~ /input/i ) && ( $ele->attrs( 'type' ) =~ /text|email|password|hidden|number/i ) ) {
208             if ( $getter ) {
209             return $ele->attrs( 'value' );
210             }
211              
212             $ele->attrs( {'value' => $value} );
213             } elsif ( ( $ele->type =~ /input/i ) && ( $ele->attrs( 'type' ) =~ /checkbox|radio/i ) ) {
214             my $collection = $dom->find( 'input[type="' . $ele->attrs( 'type' ) . '"][name="' . $ele->attrs( 'name' ) . '"]' ) || return;
215              
216             if ( $getter ) {
217             my @result = ();
218             $collection->each( sub { my $e = shift; push( @result, $e->attrs( 'value' ) ) if exists( $e->attrs()->{'checked'} ); } );
219             return wantarray ? @result : $result[0];
220             }
221              
222             $collection->each(
223             sub {
224             my $e = shift;
225             if ( ( $value eq '_on' ) || ( lc $e->attrs( 'value' ) eq lc $value ) ) {
226             $e->attrs( 'checked', 'checked' );
227             } else {
228             delete( $e->attrs()->{'checked'} );
229             }
230             }
231             );
232             } elsif ( $ele->type =~ /select/i ) {
233             my $options = $ele->find( 'option' . ( $getter ? ':checked' : '' ) ) || return;
234              
235             if ( $getter ) {
236             return $options->map( sub { my $e = shift; return $e->attrs( 'value' ) || $e->text; } );
237             }
238              
239             $options->each(
240             sub {
241             my $e = shift;
242             my $v = $e->attrs( 'value' ) || $e->text;
243              
244             if ( lc $v eq lc $value ) {
245             $e->attrs( 'selected', 'selected' );
246             } else {
247             delete( $e->attrs()->{'selected'} );
248             }
249             }
250             );
251             } elsif ( $ele->type =~ /textarea/i ) {
252             if ( $getter ) {
253             return $ele->text();
254             }
255              
256             $ele->prepend_content( $value );
257             } else {
258             die 'Unknown or Unsupported type';
259             }
260              
261             $self->update_html( $dom->to_xml );
262             } ## end sub input
263              
264             =head2 click_link()
265              
266             Posts to a URL as if a form is being submitted
267              
268             my $mech = WWW::Mechanize::Query->new();
269             $mech->post('http://www.google.com/search?q=test'); #POSTs to http://www.google.com/search with "q"
270            
271             =cut
272              
273             sub post_url () {
274             require CGI;
275              
276             my $self = shift;
277             my $url = shift;
278              
279             my $qstr = '';
280              
281             if ( $url =~ /(.*)\?(.*)/ ) {
282             $url = $1;
283             $qstr = $2;
284             }
285              
286             my $q = new CGI( $qstr );
287             my %FORM = $q->Vars();
288             my $html = qq(<form name="mainform" action="$url" method="POST">);
289              
290             foreach my $name ( keys %FORM ) {
291             $html .= qq(<input type="hidden" name="$name" value="$FORM{$name}" />);
292             }
293              
294             $html .= qq(</form>);
295              
296             $self->update_html( $html );
297             $self->current_form( 1 );
298             $self->submit();
299             } ## end sub post_url ()
300              
301             =head2 click_link()
302              
303             Checks if a L<HTML::Link> exists and if so follows it (otherwise it returns 0)
304              
305             my $mech = WWW::Mechanize::Query->new();
306             while (1) {
307             print "next page.\n";
308             last unless $mech->click_link(url_regex=>qr[/next/]);
309             }
310             =cut
311              
312             sub click_link {
313             my $self = shift;
314             return $self->find_link( @_ ) ? $self->follow_link( @_ ) : 0;
315             }
316              
317             =head2 simple_links()
318              
319             Parses L<HTML::Link> and returns simple links
320              
321             my $mech = WWW::Mechanize::Query->new();
322             $mech->get( 'http://www.amazon.com/' );
323             my @links = $mech->find_all_links();
324            
325             print $mech->simple_links(@links);
326             =cut
327              
328             sub simple_links {
329             my $self = shift;
330              
331             for my $l ( @_ ) {
332             $l = "" . ( ref( $l ) eq 'WWW::Mechanize::Image' ? $l->url() : ref( $l ) eq 'WWW::Mechanize::Link' ? $l->url_abs() : '' );
333             }
334              
335             return @_;
336             }
337              
338             =head1 SEE ALSO
339              
340             L<WWW::Mechanize>.
341              
342             L<Mojo::DOM>
343              
344             L<WWW::Mechanize::Cached>.
345              
346             =head1 AUTHORS
347              
348             =over 4
349              
350             =item *
351              
352             San Kumar (robotreply at gmail)
353              
354             =back
355              
356             =head1 COPYRIGHT AND LICENSE
357              
358             This software is copyright (c) 2012 by San Kumar.
359              
360             This is free software; you can redistribute it and/or modify it under
361             the same terms as the Perl 5 programming language system itself.
362              
363             =cut
364              
365             1;