File Coverage

blib/lib/Astro/NED/Query.pm
Criterion Covered Total %
statement 101 132 76.5
branch 23 52 44.2
condition 8 15 53.3
subroutine 18 26 69.2
pod 13 13 100.0
total 163 238 68.4


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2007 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of Astro::NED::Query
6             #
7             # Astro::NED::Query is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or (at
10             # your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package Astro::NED::Query;
23              
24 6     6   21858 use 5.006;
  6         21  
  6         248  
25 6     6   70 use strict;
  6         12  
  6         197  
26 6     6   30 use warnings;
  6         24  
  6         736  
27              
28             our $VERSION = '0.31';
29              
30 6     6   898 use autouse Carp => qw/ croak carp confess /;
  6         633  
  6         40  
31              
32 6     6   10769 use WWW::Mechanize;
  6         1504977  
  6         287  
33              
34 6     6   78 use constant NED_URL => 'http://nedwww.ipac.caltech.edu/index.html';
  6         14  
  6         6542  
35              
36             #---------------------------------------------------------------------------
37              
38              
39             # this is designed to be invoked by a subclass.
40             sub new
41             {
42 5     5 1 141 my ( $class, %fields ) = @_;
43 5   33     43 $class = ref $class || $class;
44              
45 5 50       24 croak( __PACKAGE__, '->new: illegal call to abstract base class' )
46             if $class eq __PACKAGE__;
47              
48 5         20 my $self = bless {}, $class;
49              
50             # grab top level page
51 5         57 $self->{_ua} = WWW::Mechanize->new();
52 5         147687 $self->{_ua}->get( NED_URL );
53              
54 5 50       7210210 croak( $class, '->new: error accessing NED: ',
55             $self->{_ua}->res->status_line )
56             if $self->{_ua}->res->is_error;
57              
58 5         139 $self->_init;
59 5         105 $self->save_as_defaults;
60              
61             # process fields
62 5         66 while( my ( $key, $value ) = each %fields )
63             {
64 7 50       119 croak( $class, "->new unknown attribute: `$key'" )
65             unless defined $self->field($key);
66 7         182 $self->set( $key, $value );
67             }
68              
69 5         42 return $self;
70             }
71              
72             sub _init
73             {
74 0     0   0 my ( $self ) = @_;
75 0         0 croak( ref $self, ': internal implementation error; _init undefined' );
76             }
77              
78              
79             sub field {
80              
81 32     32 1 63 my ( $self, $key ) = @_;
82              
83 32 50       158 return exists $self->Field->{$key} ? $self->Field->{$key} : undef;
84             }
85              
86              
87             #---------------------------------------------------------------------------
88              
89              
90             sub set
91             {
92 20     20 1 1137 my ( $self, $name, $value ) = @_;
93              
94 20 50       69 confess( ref $self, '->set: Wrong number of arguments' )
95             unless 3 == @_;
96              
97 20 50       96 if ( defined ( my $field = $self->field($name) ) )
98             {
99 20         258 eval {
100 20         108 $self->{_ua}->field( $field, $value );
101             };
102 20 50       302436 croak( ref $self, "->set($name): illegal value" )
103             if $@;
104             }
105             else
106             {
107 0         0 $self->{$name} = $value;
108             }
109              
110 20         128 return;
111             }
112              
113             sub get
114             {
115 5     5 1 4242 my ( $self, $name ) = @_;
116              
117 5 50       30 confess( ref $self, '->get: Wrong number of arguments' )
118             unless defined $name;
119              
120 5         41 my $field = $self->field($name);
121              
122 5 50       80 return defined($field)
123             ? $self->{_ua}->current_form->value( $field )
124             : $self->{$name};
125             }
126              
127             #---------------------------------------------------------------------------
128              
129             # map between Multiple values and form inputs.
130              
131             # HTML::Table creates a separate input for each value in a checkbox
132             # or option. this routine creates a hash matching the values to the
133             # input to make it easier to set the inputs. In some cases a single
134             # logical list of options is split into several so the GUI looks
135             # cleaner. this will merge them.
136              
137             sub _setupMultiple
138             {
139 6     6   29 my ( $self, $type, $alias, @names ) = @_;
140              
141 6         16 my %input;
142              
143 6         18 foreach my $name ( @names )
144             {
145 6 50       37 $name = qr/^$name$/ unless 'Regexp' eq ref $name;
146              
147 6         53 foreach my $input ( $self->{_ua}->current_form->inputs )
148             {
149 37722 100 100     5128505 next unless defined $input->name &&
      66        
150             $input->name =~ /$name/ && $input->type eq $type;
151              
152 12516         358958 my @value = grep { defined $_ } $input->possible_values;
  25032         158621  
153 12516 50       32253 croak( ref $self, "->setupMultiple: ($name,$type) multivalued multiple\n" )
154             if @value > 1;
155 12516         55216 $input{$value[0]} = $input;
156             }
157             }
158              
159 6         3369 $self->{_Multiple}{$alias} = \%input;
160              
161 6         53 return;
162             }
163              
164             # steal a page (well, actually code) from Class::Accessor for inputs
165             # which have multiple values
166             sub _mkMultipleAccessor {
167 2     2   8 my($self, @fields) = @_;
168 2   33     22 my $class = ref $self || $self;
169              
170 2         7 foreach my $field (@fields) {
171 6 50       63 if ( $field eq 'DESTROY' ) {
172 0         0 require Carp;
173 0         0 Carp::carp('Having a data accessor named DESTROY in '.
174             "'$class' is unwise.");
175             }
176              
177             my $accessor = sub {
178 2     2   24 my $self = shift;
179              
180 2 50       31 return 1 == @_ ?
181             $self->getMultiple( $field, @_ ) :
182             $self->setMultiple( $field, @_ );
183 6         34 };
184              
185 6         23 my $alias = "_${field}_accessor";
186              
187             ## no critic (ProhibitNoStrict)
188 6     6   52 no strict 'refs';
  6         13  
  6         12191  
189              
190 6         39 *{$class."\:\:$field"} = $accessor
  6         50  
191 6 50       8 unless defined &{$class."\:\:$field"};
192              
193 6         38 *{$class."\:\:$alias"} = $accessor
  6         44  
194 6 50       15 unless defined &{$class."\:\:$alias"};
195             }
196              
197 2         9 return;
198             }
199              
200             sub setMultiple
201             {
202 2     2 1 73 my ( $self, $name, $value, $state ) = @_;
203              
204 2 50       12 croak( ref $self, "->setMultiple: wrong number of arguments\n" )
205             unless @_ == 4;
206              
207 2 50       27 croak( ref $self, "->setMultiple: illegal value for $name: `$value'\n" )
208             unless exists $self->{_Multiple}{$name}{$value};
209              
210 2         16 my $input = $self->{_Multiple}{$name}{$value};
211              
212 2 50 33     81 if ( defined $state && $state )
213             {
214 2         12 $input->value( $value );
215             }
216             else
217             {
218 0         0 $input->value( undef );
219             }
220              
221 2         80 return;
222             }
223              
224             sub getMultiple
225             {
226 0     0 1 0 my ( $self, $name, $value ) = @_;
227              
228 0 0       0 confess( "Wrong number of arguments\n" )
229             unless @_ == 3;
230              
231 0 0       0 croak( ref $self, "->getMultiple: illegal value for $name: `$value'\n" )
232             unless exists $self->{_Multiple}{$name}{$value};
233              
234 0         0 my $input = $self->{_Multiple}{$name}{$value};
235              
236 0         0 return $input->value;
237             }
238              
239             #---------------------------------------------------------------------------
240              
241             sub possible_values
242             {
243 0     0 1 0 my ( $self, $ifield ) = @_;
244              
245 0 0       0 defined $ifield or
246             croak( ref $self, "->possible_values: missing field name\n" );
247              
248             # is this a multiple value beast?
249 0 0       0 if ( exists $self->{_Multiple}{$ifield} )
    0          
250             {
251 0         0 return keys %{$self->{_Multiple}{$ifield}}
  0         0  
252             }
253             elsif ( defined ( my $field = $self->field($ifield) ) )
254             {
255 0         0 return $self->{_ua}->current_form->find_input($field)->possible_values;
256             }
257              
258             else
259             {
260 0         0 croak( ref $self, "->possible_values: unknown field: $ifield\n" );
261             }
262             }
263              
264             #---------------------------------------------------------------------------
265              
266             sub dump
267             {
268 0     0 1 0 my ( $self ) = @_;
269              
270 0         0 $self->{_ua}->current_form->dump;
271              
272 0         0 return;
273             }
274              
275             sub form
276             {
277 0     0 1 0 my ( $self ) = @_;
278 0         0 return $self->{_ua}->current_form->form;
279             }
280              
281             #---------------------------------------------------------------------------
282              
283             sub save_as_defaults
284             {
285 6     6 1 844 my $self = shift;
286              
287             # save current form field values.
288 6         57 my @ivalues = map { [ $_ , $_->value ] } $self->{_ua}->current_form->inputs;
  12635         213021  
289 6         3214 $self->{_ivalues} = \@ivalues;
290              
291 6         39 return;
292             }
293              
294             sub set_to_defaults
295             {
296 1     1 1 1425 my $self = shift;
297              
298 1         3 for my $field ( @{$self->{_ivalues}} )
  1         5  
299             {
300 17         428 my ( $obj, $value ) = @{$field};
  17         49  
301 17 100       101 $obj->value($value)
302             if defined $value;
303             }
304              
305 1         8 return;
306             }
307              
308             # alias old names for compatibility
309             *Astro::NED::Query::reset = *set_to_defaults;
310             *Astro::NED::Query::set_default = *save_as_defaults;
311              
312              
313             #---------------------------------------------------------------------------
314              
315              
316             sub query
317             {
318 4     4 1 992 my $self = shift;
319              
320             # get class specific query presets
321 4         37 $self->_query;
322              
323 4         16 my $ua = $self->{_ua};
324              
325 4         118 $ua->click;
326              
327 4 50       2315541 if ( $ua->res->is_error )
328             {
329 0         0 $ua->back;
330 0         0 croak( ref($self), '->query: ', $ua->res->status_line );
331             }
332              
333 4         102 my $content = $ua->content;
334 4         178 $ua->back;
335              
336 4         8439 return $self->_parse_query( $content );
337             }
338              
339             sub _query
340             {
341 0     0     my ( $self ) = @_;
342 0           croak( $self, ': internal implementation error; _query undefined' );
343             }
344              
345             sub _parse_query
346             {
347 0     0     my ( $self ) = @_;
348 0           croak( ref $self,
349             ': internal implementation error; _parse_query undefined' );
350             }
351              
352             #---------------------------------------------------------------------------
353              
354             sub timeout
355             {
356 0     0 1   my ( $self, @args ) = @_;
357              
358 0           return $self->{_ua}->timeout( @args );
359             }
360              
361             #---------------------------------------------------------------------------
362             1;
363             __END__