File Coverage

blib/lib/Yahoo/Search.pm
Criterion Covered Total %
statement 87 143 60.8
branch 38 82 46.3
condition 7 30 23.3
subroutine 11 16 68.7
pod 9 9 100.0
total 152 280 54.2


line stmt bran cond sub pod time code
1             package Yahoo::Search;
2             BEGIN {
3 2     2   135596 $Yahoo::Search::VERSION = '1.11.3';
4             }
5 2     2   22 use strict;
  2         5  
  2         249  
6 2     2   13 use Carp;
  2         4  
  2         147  
7 2     2   1636 use Yahoo::Search::Request;
  2         7  
  2         16264  
8              
9             ##
10             ## This is the interface to Yahoo!'s web search API.
11             ## Written by Jeffrey Friedl
12             ##
13             ## Master source:
14             ##
15             ## http://search.cpan.org/search?mode=module&n=1&query=Yahoo::Search
16             ##
17              
18             ##
19             ## CLASS OVERVIEW
20             ##
21             ## The main class
22             ## Yahoo::Search
23             ## begets
24             ## Yahoo::Search::Request
25             ## which begets
26             ## Yahoo::Search::Response
27             ## which begets a bunch of
28             ## Yahoo::Search::Result
29             ## which beget urls, summaries, etc.
30             ##
31             ## There are plenty of "convenience functions" which appear to bypass some
32             ## of these steps as far as the user's view is concerned.
33             ##
34              
35             ##
36             ## Configuration details for each search space (Doc, Images, Video, ## etc.)...
37             ##
38             my %Config =
39             (
40             ######################################################################
41             # Normal web search
42             #
43             Doc =>
44             {
45             Url => 'http://search.yahooapis.com/WebSearchService/V1/webSearch',
46             ContextUrl => 'http://search.yahooapis.com/WebSearchService/V1/contextSearch',
47              
48             MaxCount => 50,
49              
50             ## The 'Defaults' keys indicate the universe of allowable arguments,
51             ## while the values are the defaults for those arguments.
52             Defaults => {
53             Mode => undef,
54             Context => undef,
55             Count => 10,
56             Start => 0,
57             Type => 'any',
58             AllowAdult => 0,
59             AllowSimilar => 0,
60             Language => undef,
61             Country => undef,
62             License => undef,
63             Region => undef,
64             },
65              
66             QueryOptional => 1,
67              
68             AllowedLicense => {
69             any => 1, #default
70             cc_any => 1,
71             cc_commercial => 1,
72             cc_modifiable => 1,
73             },
74              
75             AllowedMode => {
76             all => 1,
77             any => 1,
78             phrase => 1,
79             },
80              
81             AllowedType => {
82             all => 1, # deprecated
83             any => 1,
84              
85             html => 1,
86             msword => 1,
87             pdf => 1,
88             ppt => 1,
89             rss => 1,
90             txt => 1,
91             xls => 1,
92             },
93             },
94              
95              
96             ######################################################################
97             # Image search
98             #
99             Image =>
100             {
101             Url => 'http://search.yahooapis.com/ImageSearchService/V1/imageSearch',
102             MaxCount => 50,
103              
104             Defaults => {
105             Mode => undef,
106             Count => 10,
107             Start => 0,
108             Type => 'any',
109             AllowAdult => 0,
110             Color => undef,
111             },
112              
113             AllowedMode => {
114             all => 1,
115             any => 1,
116             phrase => 1,
117             },
118              
119             AllowedType => {
120             all => 1, # deprecated
121             any => 1,
122              
123             bmp => 1,
124             gif => 1,
125             jpeg => 1,
126             png => 1,
127             },
128              
129             AllowedColor => {
130             any => 1, # default
131             color => 1,
132             bw => 1,
133             }
134              
135             },
136              
137             ######################################################################
138             # Video file search
139             #
140             Video =>
141             {
142             Url => 'http://search.yahooapis.com/VideoSearchService/V1/videoSearch',
143              
144             MaxCount => 50,
145              
146             Defaults => {
147             Mode => undef,
148             Count => 10,
149             Start => 0,
150             Type => 'any',
151             AllowAdult => 0,
152             },
153              
154             AllowedMode => {
155             all => 1,
156             any => 1,
157             phrase => 1,
158             },
159              
160             AllowedType => {
161             all => 1, # deprecated
162             any => 1,
163              
164             avi => 1,
165             flash => 1,
166             mpeg => 1,
167             msmedia => 1,
168             quicktime => 1,
169             realmedia => 1,
170             },
171             },
172              
173              
174             ######################################################################
175             # "Y! Local" (like Yellow Pages) search
176             #
177             Local =>
178             {
179             Url => 'http://local.yahooapis.com/LocalSearchService/V1/localSearch',
180              
181             MaxCount => 20,
182              
183             Defaults => {
184             Count => 10,
185             Start => 0,
186             Mode => undef,
187             Radius => undef,
188             Street => undef,
189             City => undef,
190             State => undef,
191             PostalCode => undef,
192             Location => undef,
193             Lat => undef,
194             Long => undef,
195             Sort => undef,
196             },
197              
198             AllowedSort => {
199             relevance => 1,
200             distance => 1,
201             rating => 1,
202             title => 1
203             }
204             },
205              
206              
207             ######################################################################
208             # News search
209             #
210             News =>
211             {
212             Url => 'http://search.yahooapis.com/NewsSearchService/V1/newsSearch',
213              
214             MaxCount => 50,
215              
216             Defaults => {
217             Mode => undef,
218             Count => 10,
219             Start => 0,
220             Sort => undef,
221             Language => undef,
222             },
223              
224             AllowedMode => {
225             all => 1,
226             any => 1,
227             phrase => 1,
228             },
229              
230             AllowedSort => {
231             rank => 1,
232             date => 1,
233             },
234             },
235              
236              
237             Terms =>
238             {
239             Url => 'http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction',
240              
241             Defaults => {
242             Context => undef,
243             },
244             Required => {
245             Context => 1,
246             },
247             QueryOptional => 1,
248             },
249              
250             Spell =>
251             {
252             Url => 'http://search.yahooapis.com/WebSearchService/V1/spellingSuggestion',
253             },
254              
255             Related =>
256             {
257             Url => 'http://search.yahooapis.com/WebSearchService/V1/relatedSuggestion',
258             Defaults => {
259             Count => 10,
260             },
261             },
262             );
263              
264             our $UseXmlSimple = $ENV{YAHOO_SEARCH_XMLSIMPLE}; ## used in Search/Request.pm
265             our $RecentRequestUrl; ## filled in Search/Request.pm
266              
267              
268             ##
269             ## These args are allowed for any Query()
270             ##
271             my @ExtraQueryArgs = qw[AutoContinue Debug AppId];
272              
273             ##
274             ## Global defaults -- this list may be modified via import()
275             ## and Default();
276             ##
277             my %GlobalDefault =
278             (
279             ##
280             ## Debug is a string with any of: url (show the url as fetched)
281             ## xml (show the resulting xml)
282             ## hash (show the resulting hash)
283             ## stdout (show to stdout instead of stderr)
284             ## e.g. "url hash stdout"
285             Debug => "",
286              
287             ##
288             ## if AutoCarp is true (as it is by default), carp on programming errors
289             ## (but not 404s, etc.)
290             ##
291             AutoCarp => 1,
292              
293             AutoContinue => 0,
294              
295             PreRequestCallback => undef,
296             );
297              
298             ##
299             ## Helper function to set $@ and, if needed, carp.
300             ##
301             sub _carp_on_error($)
302             {
303 0     0   0 $@ = shift;
304 0 0       0 if ($GlobalDefault{AutoCarp}) {
305 0         0 carp $@;
306             }
307              
308 0         0 return ();
309             }
310              
311              
312             ##
313             ## The following private subs are used to validate arguments. They are
314             ## generally called with two args: the search space (Doc, Image, etc.), and
315             ## the text to validate.
316             ##
317             ## If called without the "text to validate" arg, they return a description
318             ## of what args are allowed (tailored to the search space, if appropriate).
319             ##
320             ## Otherwise, they return ($valid, $value).
321             ##
322             my $allow_positive_integer = sub
323             {
324             my $space = shift; # unused
325             if (not @_) {
326             return "positive integer";
327             }
328             my $val = shift;
329              
330             if (not $val =~ m/^\d+$/) {
331             return (0); # invalid: not a number
332             } elsif ($val == 0) {
333             return (0); # invalid: not positive
334             } else {
335             return (1, $val);
336             }
337             };
338              
339             my $allow_nonnegative_integer = sub
340             {
341             my $space = shift; # unused
342             if (not @_) {
343             return "non-negative integer";
344             }
345             my $val = shift;
346              
347             if (not $val =~ m/^\d+$/) {
348             return (0); # invalid: not a number
349             } else {
350             return (1, $val);
351             }
352             };
353              
354             my $allow_positive_float = sub
355             {
356             my $space = shift; # unused
357             if (not @_) {
358             return "positive number";
359             }
360             my $val = shift;
361              
362             if (not $val =~ m/^(?: \d+(?: \.\d* )?$ | \.\d+$ )/x) {
363             return (0); # invalid: not a number
364             } elsif ($val == 0) {
365             return (0); # invalid: not positive
366             } else {
367             return (1, $val);
368             }
369             };
370              
371             my $allow_float = sub
372             {
373             my $space = shift; # unused
374             if (not @_) {
375             return "number";
376             }
377             my $val = shift;
378              
379             if (not $val =~ m/^-? (?: \d+(?: \.\d* )?$ | \.\d+$ )/x) {
380             return (0); # invalid: not a number
381             } else {
382             return (1, $val);
383             }
384             };
385              
386             my $allow_country_code = sub
387             {
388             my $space = shift; # unused
389             if (not @_) {
390             return "country code";
391             }
392             my $val = shift;
393              
394             if (not $val =~ m/^(?: [a-z][a-z]$ | default$ )/x) {
395             return (0); # not a country code and not "default"
396             } else {
397             return (1, $val);
398             }
399             };
400              
401             my $allow_language_code = sub
402             {
403             my $space = shift; # unused
404             if (not @_) {
405             return "language code";
406             }
407             my $val = shift;
408              
409             if (not $val =~ m/^(?: [a-z][a-z][a-z]?$ | default$ )/x) {
410             return (0); # not a language code and not "default"
411             } else {
412             return (1, $val);
413             }
414             };
415              
416              
417             ##
418             ## This has different args than the others: it has two args ($allow_multi
419             ## and $hashref) prepended before $space
420             ##
421             my $allow_from_hash = sub
422             {
423             my $allow_multi = shift;
424             my $hashref = shift; #hash in which to check
425             my $space = shift; #unused
426              
427             if (not @_) {
428             return join '|', sort keys %$hashref;
429             }
430             my $val = shift;
431              
432             if (not $hashref) {
433             return (1, $val); # can't tell, so say it's valid
434             }
435              
436             if (not defined($val) or not length($val))
437             {
438             return (0); # not valid
439             }
440              
441             if (not $allow_multi)
442             {
443             if ($hashref->{$val}) {
444             return (1, $val); # is specifically valid
445             } else {
446             return (0); # not valid
447             }
448             }
449             else
450             {
451             my @items = split /[+,\s]+/, $val;
452             if (not @items) {
453             return (0); # not valid
454             }
455              
456             for my $item (@items)
457             {
458             if (not $hashref->{$item}) {
459             return (0); # not valid
460             }
461             }
462             return (1, $val); # valid
463             }
464             };
465              
466             my $allow_boolean = sub
467             {
468             my $space = shift; #unused
469             if (not @_) {
470             return "true or false";
471             }
472             my $val = shift;
473             return (1, $val ? 1 : 0);
474             };
475              
476             my $allow_any = sub
477             {
478             my $space = shift; #unused
479             if (not @_) {
480             return "any value";
481             }
482             my $val = shift;
483             return (1, $val);
484             };
485              
486             my $allow_postal_code = sub
487             {
488             my $space = shift; #unused
489             ## only U.S. Zone Improvement Program codes allowed
490             if (not @_) {
491             return "a US ZIP code"
492             }
493              
494             my $val = shift;
495             if ($val =~ m/^\d\d\d\d\d(?:-?\d\d\d\d)?$/) {
496             return (1, $val);
497             } else {
498             return (0);
499             }
500             };
501              
502             my $allow_coderef = sub
503             {
504             my $space = shift; #unused
505             my $val = shift;
506             if (ref($val) eq 'CODE') {
507             return (1, $val);
508             } else {
509             return (0);
510             }
511             };
512              
513             my $allow_appid = sub
514             {
515             my $space = shift; #unused
516              
517             if (not @_) {
518             return "something which matches /^[- A-Za-z0-9_()[\\]*+=,.:\@\\\\]{8,}\$/";
519             }
520              
521             my $val = shift;
522             if ($val =~ m/^[- A-Za-z0-9_()\[\]*+=,.:\@\\]{8,}$/) {
523             return (1, $val);
524             } else {
525             return (0);
526             }
527             };
528              
529             our %KnownLanguage =
530             (
531             default => 'any/all languages',
532              
533             ar => 'Arabic',
534             bg => 'Bulgarian',
535             ca => 'Catalan',
536             szh => 'Chinese (simplified)',
537             tzh => 'Chinese (traditional)',
538             hr => 'Croatian',
539             cs => 'Czech',
540             da => 'Danish',
541             nl => 'Dutch',
542             en => 'English',
543             et => 'Estonian',
544             fi => 'Finnish',
545             fr => 'French',
546             de => 'German',
547             el => 'Greek',
548             he => 'Hebrew',
549             hu => 'Hungarian',
550             is => 'Icelandic',
551             it => 'Italian',
552             ja => 'Japanese',
553             ko => 'Korean',
554             lv => 'Latvian',
555             lt => 'Lithuanian',
556             no => 'Norwegian',
557             fa => 'Persian',
558             pl => 'Polish',
559             pt => 'Portuguese',
560             ro => 'Romanian',
561             ru => 'Russian',
562             sk => 'Slovak',
563             sl => 'Slovenian',
564             es => 'Spanish',
565             sv => 'Swedish',
566             th => 'Thai',
567             tr => 'Turkish',
568             );
569              
570             our %KnownCountry =
571             (
572             default => "any/all countries",
573              
574             ar => 'Argentina',
575             au => 'Australia',
576             at => 'Austria',
577             be => 'Belgium',
578             br => 'Brazil',
579             ca => 'Canada',
580             cn => 'China',
581             cz => 'Czech Republic',
582             dk => 'Denmark',
583             fi => 'Finland',
584             fr => 'France',
585             de => 'Germany',
586             it => 'Italy',
587             jp => 'Japan',
588             kr => 'Korea',
589             nl => 'Netherlands',
590             no => 'Norway',
591             pl => 'Poland',
592             rf => 'Russian Federation',
593             es => 'Spain',
594             se => 'Sweden',
595             ch => 'Switzerland',
596             tw => 'Taiwan',
597             uk => 'United Kingdom',
598             us => 'United States',
599             );
600              
601              
602             ##
603             ## Mapping from arg name to value validation routine.
604             ##
605             my %ValidateRoutine =
606             (
607             Count => $allow_positive_integer,
608             Start => $allow_nonnegative_integer,
609              
610             Radius => $allow_positive_float,
611              
612             AllowAdult => $allow_boolean,
613             AllowSimilar => $allow_boolean,
614              
615             Context => $allow_any,
616              
617             Street => $allow_any,
618             City => $allow_any,
619             State => $allow_any,
620             Location => $allow_any,
621             Lat => $allow_float,
622             Long => $allow_float,
623              
624             PostalCode => $allow_postal_code,
625             Language => $allow_language_code,
626             Country => $allow_country_code,
627             Region => $allow_country_code,
628              
629             Mode => sub { $allow_from_hash->(0, $Config{$_[0]}->{AllowedMode}, @_) },
630             Sort => sub { $allow_from_hash->(0, $Config{$_[0]}->{AllowedSort}, @_) },
631             Type => sub { $allow_from_hash->(0, $Config{$_[0]}->{AllowedType}, @_) },
632             License => sub { $allow_from_hash->(1, $Config{$_[0]}->{AllowedLicense}, @_) },
633             Color => sub { $allow_from_hash->(0, $Config{$_[0]}->{Color}, @_) },
634              
635             Debug => $allow_any,
636             AutoContinue => $allow_boolean,
637             AutoCarp => $allow_boolean,
638              
639             AppId => $allow_appid,
640              
641             PreRequestCallback => $allow_coderef,
642             );
643              
644             ##
645             ## returns ($newvalue, $error);
646             ##
647             sub _validate($$$;$)
648             {
649 31     31   43 my $global = shift; # true if for a global setting
650 31         35 my $space = shift; # Doc, Image, etc.
651 31         32 my $key = shift; # "Count", "State", etc.
652 31 100       68 my $have_val = @_ ? 1 : 0;
653 31         61 my $val = shift;
654              
655 31 50       300 if (not $ValidateRoutine{$key}) {
656 0         0 return (undef, "unknown argument '$key'");
657             }
658              
659 31 50 66     136 if (not $global and $key eq 'AutoCarp') {
660 0         0 return (undef, "AutoCarp is a global setting which can not be used in this context");
661             }
662              
663 31 100       74 if (not $have_val) {
664 3         9 return (1);
665             }
666              
667 28         73 my ($valid, $newval) = $ValidateRoutine{$key}->($space, $val);
668              
669 28 50       133 if ($valid) {
670 28         70 return ($newval, undef);
671             }
672              
673 0         0 my $expected = $ValidateRoutine{$key}->($space);
674 0 0       0 if ($space) {
675 0         0 return (undef, "invalid value \"$val\" for $space\'s \"$key\" argument, expected: $expected");
676             } else {
677 0         0 return (undef, "invalid value \"$val\" for \"$key\" argument, expected: $expected");
678             }
679             }
680              
681              
682             ##
683             ## 'import' accepts key/value pairs:
684             ##
685             sub import
686             {
687 2     2   26 my $class = shift;
688              
689 2 50       15 if (@_ % 2 != 0) {
690 0         0 Carp::confess("bad number of args to 'use $class'");
691             }
692 2         17 my %Args = @_;
693              
694 2         32 while (my ($key, $val) = each %Args)
695             {
696 4         11 my ($newval, $error) = _validate(1, undef, $key, $val);
697 4 50       36 if ($error) {
698 0         0 Carp::confess("$error, in 'use $class'");
699             } else {
700 4         11950 $GlobalDefault{$key} = $newval;
701             }
702             }
703             }
704              
705              
706             ##
707             ## Get (or set) one of the default global values. They can be set this way
708             ## (either as Yahoo::Search->Default or $SearchEngine->Default), or via
709             ## Yahoo::Search->new(), or on the 'use' line.
710             ##
711             ## When used with a $SearchEngine object, the value returned is the value
712             ## in effect, which is the global one if the $SearchEngine does not have
713             ## one itself.
714             ##
715             sub Default
716             {
717 3     3 1 36 my $class_or_obj = shift; # Yahoo::Search->Default or $SearchEngine->Default
718 3         7 my $key = shift;
719 3 50       14 my $have_val = @_ ? 1 : 0;
720 3         8 my $val = shift;
721              
722 3         7 my $global = not ref $class_or_obj;
723              
724 3         5 my $old;
725 3 50 33     23 if ($global or not exists $class_or_obj->{$key}) {
726 3         8 $old = $GlobalDefault{$key};
727             } else {
728 0         0 $old = $class_or_obj->{$key};
729             }
730              
731 3 50       10 if ($have_val)
732             {
733 0         0 my ($newval, $error) = _validate($global, undef, $key, $val);
734 0 0       0 if ($error) {
735 0         0 return _carp_on_error($error);
736             }
737              
738 0 0       0 if (ref $class_or_obj) {
739 0         0 $class_or_obj->{$key} = $newval;
740             } else {
741 0         0 $GlobalDefault{$key} = $newval;
742             }
743             }
744             else
745             {
746 3         9 my ($okay, $error) = _validate($global, undef, $key);
747 3 50       11 if ($error) {
748 0         0 return _carp_on_error($error);
749             }
750             }
751              
752 3         18 return $old;
753             }
754              
755              
756              
757             ##
758             ## Maps Yahoo::Search->Query arguments to Y! API parameters.
759             ##
760             my %ArgToParam =
761             (
762             AllowAdult => 'adult_ok',
763             AllowSimilar => 'similar_ok',
764             AppId => 'appid',
765             City => 'city',
766             Context => 'context',
767             Count => 'results',
768             Country => 'country',
769             Color => 'coloration',
770             Language => 'language',
771             Lat => 'latitude',
772             License => 'license',
773             Location => 'location',
774             Long => 'longitude',
775             Mode => 'type',
776             PostalCode => 'zip',
777             Radius => 'radius',
778             Region => 'region',
779             Sort => 'sort',
780             Start => 'start',
781             State => 'state',
782             Street => 'street',
783             Type => 'format',
784             );
785              
786              
787             ##
788             ## The search-engine constructor.
789             ##
790             ## No args are needed, but any of %ValidateRoutine keys except AutoCarp are
791             ## allowed (they'll be used as the defaults when queries are later
792             ## constructed via this object).
793             ##
794             sub new
795             {
796 3     3 1 6 my $class = shift;
797              
798 3 50       17 if (@_ % 2 != 0) {
799 0         0 return _carp_on_error("wrong arg count to $class->new");
800             }
801              
802 3         8 my $SearchEngine = { @_ };
803              
804 3         13 for my $key (keys %$SearchEngine)
805             {
806 0         0 my ($newval, $error) = _validate(0, undef, $key, $SearchEngine->{$key});
807 0 0       0 if ($error) {
808 0         0 return _carp_on_error("$error, in call to $class->new");
809             }
810 0         0 $SearchEngine->{$key} = $newval;
811             }
812              
813 3         16 return bless $SearchEngine, $class;
814             }
815              
816             ##
817             ## Request method (can also be called like a constructor).
818             ## Specs to a specific query are provided, and a Request object is returned.
819             ##
820             sub Request
821             {
822 3     3 1 8 my $SearchEngine = shift; # self
823 3         7 my $SearchSpace = shift; # "Doc", "Image", "News", etc..
824 3         5 my $QueryText = shift; # "Briteny", "egregious compensation semel", etc.
825              
826 3 50       14 if (@_ % 2 != 0) {
827 0         0 return _carp_on_error("wrong arg count");
828             }
829              
830 3         11 my %Args = @_;
831              
832 3 50 33     31 if (not defined $SearchSpace or not $Config{$SearchSpace}) {
833 0         0 my $list = join '|', sort keys %Config;
834 0         0 return _carp_on_error("bad search-space identifier, expecting one of: $list");
835             }
836              
837             ##
838             ## Ensure that required args are there
839             ##
840 3 50       15 if (my $ref = $Config{$SearchSpace}->{Required})
841             {
842 0         0 for my $arg (keys %$ref)
843             {
844 0 0 0     0 if (not defined($Args{$arg}) or not length($Args{$arg})) {
845 0         0 return _carp_on_error("argument '$arg' required");
846             }
847             }
848             }
849              
850              
851             ##
852             ## %Param holds the key/vals we'll send in the request to Yahoo!
853             ##
854 3         7 my %Param;
855              
856             ##
857             ## Special case for some searches: query not required
858             ##
859 3 50 33     29 if (not defined($QueryText) or length($QueryText) == 0)
860             {
861 0 0 0     0 if ($Args{Context} and $Config{$SearchSpace}->{QueryOptional}) {
862             ## query text not required
863             } else {
864 0         0 return _carp_on_error("missing query");
865             }
866             }
867             else
868             {
869             ## normal query
870 3         12 $Param{query} = $QueryText;
871             }
872              
873              
874             ##
875             ## This can be called as a constructor -- if so, $SearchEngine will be
876             ## the class name, and we'll want to turn into an object.
877             ##
878 3 50       12 if (not ref $SearchEngine) {
879 0         0 $SearchEngine = $SearchEngine->new();
880             }
881              
882 3         431 my %OtherRequestArgs;
883              
884             ##
885             ## Go through most allowed args, taking the value from this call's arg
886             ## list if provided, from the defaults that were registered with the
887             ## SearchEngine, or failing those, the defaults for this type of query.
888             ##
889 3         6 for my $key (keys %{ $Config{$SearchSpace}->{Defaults} }, @ExtraQueryArgs)
  3         29  
890             {
891             ##
892             ## Isolate the value we'll use for this request: from our args,
893             ## from the defaults registered with the search-engine, or from
894             ## the search-space defaults.
895             ##
896 42         43 my $val;
897 42 100       469 if (exists $Args{$key}) {
    50          
    100          
    50          
898 2         6 $val = delete $Args{$key};
899             } elsif (exists $SearchEngine->{$key}) {
900 0         0 $val = $SearchEngine->{$key};
901             } elsif (exists $GlobalDefault{$key}) {
902 10         21 $val = $GlobalDefault{$key};
903             } elsif (exists $Config{$SearchSpace}->{Defaults}->{$key}) {
904 30         59 $val = $Config{$SearchSpace}->{Defaults}->{$key};
905             } else {
906 0         0 $val = undef;
907             }
908              
909 42 100       91 if (defined $val)
910             {
911 24         55 my ($newval, $error) = _validate(0, $SearchSpace, $key, $val);
912              
913 24 50       57 if ($error) {
914 0         0 return _carp_on_error($error);
915             }
916              
917 24 100       62 if (my $param = $ArgToParam{$key}) {
918 18         128 $Param{$param} = $newval;
919             } else {
920 6         19 $OtherRequestArgs{$key} = $newval;
921             }
922             }
923             }
924              
925             ##
926             ## Any leftover args are bad
927             ##
928 3 50       16 if (%Args) {
929 0         0 my $list = join(', ', keys %Args);
930 0         0 return _carp_on_error("unknown args for '$SearchSpace' query: $list");
931             }
932              
933             ##
934             ## An AppId is required for all calls
935             ##
936 3 50       89 if (not $Param{'appid'})
937             {
938 0         0 return _carp_on_error("an AppId is required -- please make one up");
939             }
940              
941             ##
942             ## Do some special per-arg-type processing
943             ##
944              
945             ##
946             ## If we're doing a Doc context search, be sure to use the proper
947             ## action url
948             ##
949 3         10 my $ActionUrl = $Config{$SearchSpace}->{Url};
950              
951 3 50 33     16 if ($Param{context} and $Config{$SearchSpace}->{ContextUrl}) {
952 0         0 $ActionUrl = $Config{$SearchSpace}->{ContextUrl};
953             }
954              
955             ##
956             ## Ensure that the Count, if given, is not over max
957             ## No longer enforced here. MaxCount is only advisory now.
958             #if (defined $Param{count} and $Param{count} > $Config{$SearchSpace}->{MaxCount}) {
959             # return _carp_on_error("maximum allowed Count for a $SearchSpace search is $Config{$SearchSpace}->{MaxCount}");
960             #}
961              
962             ##
963             ## If License is given, it an have multiple values (space, comma, or
964             ## plus-separated).
965             ##
966 3 50       10 if ($Param{license}) {
967 0         0 $Param{license} = [ split /[+,\s]+/, $Param{license} ];
968             }
969              
970             ##
971             ## In Perl universe, Start is 0-based, but the Y! API's "start" is 1-based.
972             ##
973 3         9 $Param{start}++;
974              
975             # 'Local' has special required parameters
976 3 50 0     14 if ($SearchSpace eq 'Local'
      33        
977             and not
978             ## the following are the allowed parameter sets... if one is there,
979             ## we're okay
980             ($Param{location}
981             or
982             $Param{'zip'}
983             or
984             ($Param{'state'} and $Param{'city'})
985             or
986             (defined($Param{'latitude'}) and defined($Param{'longitude'}))
987             ))
988             {
989             ##
990             ## The diff between $Param{} references in the if() above, and
991             ## the arg names in the error below, is the %ArgToParam mapping
992             ##
993 0         0 return _carp_on_error("a 'Local' query must have at least Lat+Long, Location, PostalCode, or City+State");
994             }
995              
996             ##
997             ## Okay, we have everything we need to make a specific request object.
998             ## Make it and return.
999             ##
1000 3         44 return Yahoo::Search::Request->new(
1001             SearchEngine => $SearchEngine,
1002             Space => $SearchSpace,
1003             Action => $ActionUrl,
1004             Params => \%Param,
1005             %OtherRequestArgs,
1006             );
1007             }
1008              
1009             ##
1010             ## A way to bypass an explicit Request object, jumping from a SearchEngine
1011             ## (or nothing) directly to a Response object.
1012             ##
1013             sub Query
1014             {
1015 3     3 1 8 my $SearchEngine = shift;
1016             ##
1017             ## Can be called as a constructor -- if so, $SearchEngine will be the
1018             ## class name
1019             ##
1020 3 50       15 if (not ref $SearchEngine) {
1021 3         18 $SearchEngine = $SearchEngine->new();
1022             }
1023              
1024 3 50       197 if (my $Request = $SearchEngine->Request(@_)) {
1025 3         16 return $Request->Fetch();
1026             } else {
1027             # $@ already set
1028 0         0 return ();
1029             }
1030             }
1031              
1032              
1033             ##
1034             ## A way to bypass explicit Request and Response objects, jumping from a
1035             ## SearchEngine (or nothing) directly to a list of Result objects.
1036             ##
1037             sub Results
1038             {
1039 3     3 1 6226 my $Response = Query(@_);
1040              
1041 3 50       101 if (not $Response) {
1042             # $@ already set
1043 3         17 return ();
1044             }
1045 0           return $Response->Results;
1046             }
1047              
1048             ##
1049             ## A way to bypass explicit Request and Response objects, jumping from a
1050             ## SearchEngine (or nothing) directly to a list of links.
1051             ##
1052             sub Links
1053             {
1054 0     0 1   return map { $_->Link } Results(@_);
  0            
1055             }
1056              
1057              
1058             ##
1059             ## A way to bypass explicit Request and Response objects, jumping from a
1060             ## SearchEngine (or nothing) directly to a bunch of html results.
1061             ##
1062             sub HtmlResults
1063             {
1064 0     0 1   return map { $_->as_html } Results(@_);
  0            
1065             }
1066              
1067             ##
1068             ## A way to bypass explicit Request and Response objects, jumping from a
1069             ## SearchEngine (or nothing) directly to a list of terms
1070             ## (For Spell, Related, and Terms searches)
1071             ##
1072             sub Terms
1073             {
1074 0     0 1   return map { $_->Term } Results(@_);
  0            
1075             }
1076              
1077              
1078             sub MaxCount
1079             {
1080 0 0   0 1   if (@_) {
1081             ##
1082             ## We'll use only the last arg -- it can be called as either
1083             ## Yahoo::Search::MaxCount($SearchSpace) or
1084             ## Yahoo:Search->MaxCount($SearchSpace) and we don't care which.
1085             ## In either case, the final arg is the search space.
1086             ##
1087 0           my $SearchSpace = $_[-1];
1088 0 0 0       if ($Config{$SearchSpace} and $Config{$SearchSpace}->{MaxCount}) {
1089 0           return $Config{$SearchSpace}->{MaxCount};
1090             }
1091             }
1092 0           return (); # bad/missing arg
1093             }
1094              
1095              
1096              
1097             1;
1098             __END__