Changes to WWW-Mechanize-Query proposal - small fix 1
[email protected] (Nikolay Chegodaev) Tue, 30 Jun 2020 14:33:41 +0300
| Newsgroups | perl.module-authors |
|---|---|
| Message-ID | <CAEgowzvscE84Ko=rqvf3-2xuj3QiNNrY3ytd5Rwt0D+ODL6ZhA@mail.gmail.com> |
Hi San,
Hi Module Authors,
My name is Nikolay. I am a Perl enthusiast and worked as Perl Developer for
5 years. I am writing to you because of the Olaf Alders advice - the Gitpan
repository keeper - https://github.com/gitpan. He told me that you may help
to publish changes for the module to reflect the recent changes in
Mojo::DOM, WWW::Mechanize::Query is dependent on Mojo::DOM or so it seems.
The module is a really good approach and I do not think it would be good to
leave it like this. Writing to both superasn email and module authors in
the hope that some email would answer.
have tried to use https://github.com/gitpan/WWW-Mechanize-Query to
implement a really small project on Perl v5.26.1 and encountered a number
of errors which prevent me from using the module. In fact errors probably
reflect recent changes in https://metacpan.org/pod/Mojo::DOM module which
is a dependence for WWW-Mechanize-Query.
I have made some testing changes to the module Query.pm on my localhost,
which solved the issue for me. *Do you mind if I share them public and
commit them to the repository, with your code review of course?*
The changes are seem to be small and mostly related to sub input of the
Query.pm module ( link to Gitpan project
<https://github.com/gitpan/WWW-Mechanize-Query/blob/master/lib/WWW/Mechanize/Query.pm>
)
for Perl v5.26.1 and Mojo::DOM <https://metacpan.org/pod/Mojo::DOM> the
changes list for now only seem to include the following:
1) change all attrs calls to attr call - for example $ele->attrs( 'type' )
to $ele->attr( 'type' )
2) change ->type call to ->tag call at the place where we check if the type
of the element submitted is correct, but the information we check contains
in ->tag attribute, not in the ->type attribute. -> type attribute only
contains a "tag" in it since select or text boxes are considered like so in
the new edition of Mojo::DOM it seems. For example: *} elsif ( $ele->type
=~ /select/i ) { should be changed to } elsif ( $ele->tag =~ /select/i ) {*
3) change $dom->to_xml to just just dom at the response of the sub input
for example: $self->update_html( $dom->to_xml ); to $self->update_html(
$dom); to_xml seem not to be used in Mojo::DOM as far as I tested and it
seems like just $dom already contains the xml content
Please kindly find attached Query.pm with the changes - I have taken an
audacity to change the version, please accept apologies if I should not.
The attached module reflects the changes mentioned in the fields 1)-3). I
have tested them on my localhost - seems working. Also while the module
being used in combo with the WWW::Mechanize there are issues with the
posting the forms - older format of form fields in there so debugging it at
the moment, while working on my script/snippet - so probably a second fix
to follow.
Thanks.
Kind Regards,
Nikolay.
Query.pm
(application/x-perl, 8.8 KB)
use strict;
use warnings;
package WWW::Mechanize::Query;
=head1 NAME
WWW::Mechanize::Query - CSS3 selectors (or jQuery like CSS selectors) for WWW::Mechanize.
=head1 VERSION
Version 0.03
=cut
our $VERSION = '0.04';
=head1 SYNOPSIS
use WWW::Mechanize::Query;
my $mech = WWW::Mechanize::Query->new( -ignore_cache => 0, -debug => 0 );
$mech->get( 'http://www.amazon.com/' );
$mech->input( 'input[type="text"][name="field-keywords"]', 'Perl' );
$mech->submit();
print $mech->at('h2.resultCount')->span->text; #prints "Showing 1 - 16 of 7,104 Results"
=head1 DESCRIPTION
This module combines L<WWW::Mechanize> with L<Mojo::DOM> making it possible to fill forms and scrape web with help of CSS3 selectors.
For a full list of supported CSS selectors please see L<Mojo::DOM::CSS>.
=cut
use parent qw(WWW::Mechanize::Cached);
use Data::Dumper;
use Mojo::DOM;
use Regexp::Common qw /URI/;
=head1 CONSTRUCTOR
=head2 new
Creates a new L<WWW::Mechanize>'s C<new> object with any passed arguments.
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).
my $mech = WWW::Mechanize::Query->new( ignore_cache => 0, agent => 'LWP' );
=cut
sub new {
my $class = shift;
my %mech_args = @_;
if ( !$mech_args{agent} ) {
$mech_args{agent} = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko/20100101 Firefox/11.0';
}
my $self = $class->SUPER::new( %mech_args );
if ( $mech_args{'-ignore_cache'} ) {
$self->{ignore_cache} = 1;
}
$self->{'_internal'}->{'mojo'} = Mojo::DOM->new();
$self->cookie_jar->{ignore_discard} = 1;
return $self;
}
sub _make_request {
my $self = shift;
my $request = shift;
my $response = undef;
my $cache = !$self->{ignore_cache};
my $log = '';
unless ( $self->{debug} ) {
my $str = "" . $request->as_string;
my $uri = $str =~ m[(http.*)] ? $1 : $str;
$log .= "Mech Debug: " . $uri;
}
if ( !$cache ) {
my $req = $request;
if ( !$self->ref_in_cache_key ) {
my $clone = $request->clone;
$clone->header( Referer => undef );
$req = $clone->as_string;
}
$self->cache->remove( $req );
}
$response = $self->SUPER::_make_request( $request, @_ );
unless ( $self->{debug} ) {
$log .= " (cached: " . ( $self->is_cached() ? 1 : 0 ) . ", status: " . $response->code . ")\n";
open( SAV, ">>c:/mechanize.log" ) and print( SAV $log ) and close( SAV );
if ( $self->{'-debug'} ) {
print $log;
}
}
return $response;
} ## end sub _make_request
=head1 METHODS
Methods provided by L<WWW::Mechanize> can be accessed directly.
Methods provided by L<Mojo::DOM> are accessible by calling I<dom()> method.
=head2 dom()
Parses the current content and returns a L<Mojo::DOM> object.
my $dom = $mech->dom;
print $dom->to_xml();
=cut
sub dom {
my $self = shift;
my $content = $self->content;
if ( !$self->{'_internal'}->{'_last_content'} || ( $content ne $self->{'_internal'}->{'_last_content'} ) || !$self->{'_internal'}->{'_last_dom'} ) {
$self->{'_internal'}->{'_last_content'} = $content;
$self->{'_internal'}->{'_last_dom'} = $self->{'_internal'}->{'mojo'}->parse( $content );
}
return $self->{'_internal'}->{'_last_dom'};
}
=head2 at()
Parses the current content and returns a L<Mojo::DOM> object using CSS3 selectors.
my $mech = WWW::Mechanize::Query->new();
$mech->get( 'http://www.amazon.com/' );
print $mech->at( 'div > h2' )->text;
=cut
sub at {
my $self = shift;
my $expr = shift;
return $self->dom->at( $expr );
}
=head2 find()
Parses the current content and returns a L<Mojo::DOM> collection using CSS3 selectors.
my $mech = WWW::Mechanize::Query->new();
$mech->get( 'http://www.amazon.com/' );
print $mech->find( 'div > h2' )->each ( sub { print shift->all_text; } );
=cut
sub find {
my $self = shift;
my $expr = shift;
return $self->dom->find( $expr );
}
=head2 input()
Gets or sets Form fields using CSS3 selectors.
my $mech = WWW::Mechanize::Query->new();
$mech->get( 'http://www.imdb.com/' );
$mech->input( 'input[name="q"]', 'lost' ); #fill name
$mech->input( 'select[name="s"]', 'ep' ); #select "TV" from drop-down list
$mech->submit();
print $mech->content;
print $mech->input( 'input[name="q"]' ); #prints "lost";
#TODO: Right now it fills out the first matching field but should be restricted to selected form.
=cut
sub input {
my $self = shift;
my $ele = shift;
my $value = shift;
my $getter = !defined( $value );
my $o = $ele;
if ( ref( $ele ) ne 'Mojo::DOM' ) {
$ele = $self->at( $ele );
}
die "No '$o' exists" unless $ele;
die "Not supported" unless ( $ele->tag =~ /input|select|textarea/i );
my $dom = $self->dom;
if ( ( $ele->tag =~ /input/i ) && ( $ele->attr( 'type' ) =~ /text|email|password|hidden|number/i ) ) {
if ( $getter ) {
return $ele->attr( 'value' );
}
$ele->attr( {'value' => $value} );
} elsif ( ( $ele->tag =~ /input/i ) && ( $ele->attr( 'type' ) =~ /checkbox|radio/i ) ) {
my $collection = $dom->find( 'input[type="' . $ele->attr( 'type' ) . '"][name="' . $ele->attr( 'name' ) . '"]' ) || return;
if ( $getter ) {
my @result = ();
$collection->each( sub { my $e = shift; push( @result, $e->attr( 'value' ) ) if exists( $e->attr()->{'checked'} ); } );
return wantarray ? @result : $result[0];
}
$collection->each(
sub {
my $e = shift;
if ( ( $value eq '_on' ) || ( lc $e->attr( 'value' ) eq lc $value ) ) {
$e->attr( 'checked', 'checked' );
} else {
delete( $e->attr()->{'checked'} );
}
}
);
} elsif ( $ele->tag =~ /select/i ) {
my $options = $ele->find( 'option' . ( $getter ? ':checked' : '' ) ) || return;
if ( $getter ) {
return $options->map( sub { my $e = shift; return $e->attr( 'value' ) || $e->text; } );
}
$options->each(
sub {
my $e = shift;
my $v = $e->attr( 'value' ) || $e->text;
if ( lc $v eq lc $value ) {
$e->attr( 'selected', 'selected' );
} else {
delete( $e->attr()->{'selected'} );
}
}
);
} elsif ( $ele->tag =~ /textarea/i ) {
if ( $getter ) {
return $ele->text();
}
$ele->prepend_content( $value );
} else {
die 'Unknown or Unsupported type';
}
print $dom . "\n";
$self->update_html( $dom );
} ## end sub input
=head2 click_link()
Posts to a URL as if a form is being submitted
my $mech = WWW::Mechanize::Query->new();
$mech->post('http://www.google.com/search?q=test'); #POSTs to http://www.google.com/search with "q"
=cut
sub post_url () {
require CGI;
my $self = shift;
my $url = shift;
my $qstr = '';
if ( $url =~ /(.*)\?(.*)/ ) {
$url = $1;
$qstr = $2;
}
my $q = new CGI( $qstr );
my %FORM = $q->Vars();
my $html = qq(<form name="mainform" action="$url" method="POST">);
foreach my $name ( keys %FORM ) {
$html .= qq(<input type="hidden" name="$name" value="$FORM{$name}" />);
}
$html .= qq(</form>);
$self->update_html( $html );
$self->current_form( 1 );
$self->submit();
} ## end sub post_url ()
=head2 click_link()
Checks if a L<HTML::Link> exists and if so follows it (otherwise it returns 0)
my $mech = WWW::Mechanize::Query->new();
while (1) {
print "next page.\n";
last unless $mech->click_link(url_regex=>qr[/next/]);
}
=cut
sub click_link {
my $self = shift;
return $self->find_link( @_ ) ? $self->follow_link( @_ ) : 0;
}
=head2 simple_links()
Parses L<HTML::Link> and returns simple links
my $mech = WWW::Mechanize::Query->new();
$mech->get( 'http://www.amazon.com/' );
my @links = $mech->find_all_links();
print $mech->simple_links(@links);
=cut
sub simple_links {
my $self = shift;
for my $l ( @_ ) {
$l = "" . ( ref( $l ) eq 'WWW::Mechanize::Image' ? $l->url() : ref( $l ) eq 'WWW::Mechanize::Link' ? $l->url_abs() : '' );
}
return @_;
}
=head1 SEE ALSO
L<WWW::Mechanize>.
L<Mojo::DOM>
L<WWW::Mechanize::Cached>.
=head1 AUTHORS
=over 4
=item *
San Kumar (robotreply at gmail)
=back
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by San Kumar.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
1;