File Coverage

blib/lib/SWISH/WebService.pm
Criterion Covered Total %
statement 80 130 61.5
branch 12 46 26.0
condition 10 25 40.0
subroutine 17 21 80.9
pod 1 8 12.5
total 120 230 52.1


line stmt bran cond sub pod time code
1             package SWISH::WebService;
2              
3 1     1   25955 use strict;
  1         2  
  1         37  
4 1     1   6 use warnings;
  1         2  
  1         27  
5 1     1   4 use Carp;
  1         6  
  1         90  
6 1     1   3289 use Data::Dumper;
  1         31692  
  1         95  
7 1     1   1167 use URI::Escape;
  1         1469  
  1         63  
8 1     1   824 use Data::Pageset;
  1         33340  
  1         13  
9 1     1   30150 use Template;
  1         51816  
  1         38  
10 1     1   13394 use Search::Tools;
  1         6325  
  1         47  
11 1     1   1927 use Search::Tools::XML;
  1         169885  
  1         103  
12 1     1   2282 use Time::HiRes qw( gettimeofday tv_interval );
  1         4498  
  1         12  
13              
14 1     1   375 use base qw( Class::Accessor::Fast );
  1         4  
  1         2321  
15              
16             our $VERSION = '0.02';
17             our $Props = 'swishrank,swishreccount,swishdocpath,swishtitle,swishdescription';
18             our $Server = 'http://localhost/swished/';
19             our %Headers = (); # cache from index on first connect
20             our $Index = 'DEFAULT';
21             our $Debug = $ENV{PERL_DEBUG} || 0;
22             our %Defaults = (
23             q => '',
24             o => '',
25             n => 10,
26             p => 10,
27             s => 1,
28             f => 'html',
29             );
30              
31             our %Content_Types = (
32             xml => 'application/xml',
33             html => 'text/html',
34             simple => 'text/plain',
35             rss => 'application/xml'
36             );
37              
38             our $XMLDecl = '';
39              
40             ##################################################################################
41             # Templates
42             # could break these into separate files but easier for maintenance to include here
43             #
44              
45             our %Templates = ();
46              
47             ######################## XML ##############################
48              
49             $Templates{xml} = <
50             $XMLDecl
51            
52            
53             [% count = 0;
54             WHILE (r = results.NextResult);
55             count = count + 1 %]
56            
57             id="[% r.Property('swishreccount') %]"
58             rank="[% r.Property('swishrank') %]">
59             [% self.xml.utf8_safe( r.Property('swishtitle') ) %]
60             [% r.Property('swishdocpath') | uri %]
61             [% self.hiliter.light(
62             self.xml.utf8_safe(
63             self.snipper.snip(
64             r.Property('swishdescription')
65             )
66             )
67             )
68             %]
69            
70             [% LAST IF count == self.p %]
71             [% END %]
72            
73            
74             start="[% self.s %]"
75             end="[% self.s + self.p - 1 %]"
76             max="[% self.p %]"
77             total="[% results.Hits %]"
78             runtime="[% self.elapsed | format('%0.3f') %]"
79             searchtime="[% self.searchtime | format('%0.3f') %]"
80             >
81             [% results.Query %]
82             [% results.Stopwords %]
83             [% self.o %]
84             [% self.wordchars %]
85             [% self.beginchars %]
86             [% self.endchars %]
87            
88            
89             [% UNLESS pager.current_page == pager.first_page %]
90            
91             Prev
92            
93             [% IF pager.current_page > pager.first_page %]
94            
95             [% pager.first_page %]
96            
97             [% END %]
98             [% END %]
99             [% FOR p = pager.pages_in_set %]
100             [% IF p == pager.current_page %]
101             [% p %]
102             [% ELSE %]
103            
104             [% p %]
105            
106             [% END %]
107             [% END %]
108             [% UNLESS pager.current_page == pager.last_page %]
109             [% IF pager.current_page < pager.last_page %]
110            
111             [% pager.last_page %]
112            
113             [% END %]
114             Next
115             [% END %]
116            
117            
118             XML
119              
120             ###################### HTML ###########################
121              
122             $Templates{html} = <
123            
124            
125             [% count = 0;
126             WHILE (r = results.NextResult);
127             count = count + 1 %]
128            
129             id="item_[% r.Property('swishreccount') %]"
130             class="item" >
131             [% r.Property('swishrank') %]
132            
133             [%
134             self.xml.utf8_safe(
135             r.Property('swishtitle')
136             )
137             %]
138            
139             [% r.Property('swishdocpath') | uri %]
140             [% self.hiliter.light(
141             self.xml.utf8_safe(
142             self.snipper.snip(
143             r.Property('swishdescription')
144             )
145             )
146             )
147             %]
148            
149             [% LAST IF count == self.p %]
150             [% END %]
151            
152            
153            
154             Results [% self.s %] - [% self.s + self.p - 1 %]
155             of [% results.Hits %]
156            
157             for [% self.snipper.rekw.keywords.join(' ') %]
158             [% IF results.Stopwords %]
159             The following words were automatically removed:
160             [% results.Stopwords %]
161            
162             [% END %]
163             [% IF self.o %]
164             Sorted by: [% self.o %]
165             [% END %]
166            
167             Run time: [% self.elapsed | format('%0.3f') %] sec -
168             Search time: [% self.searchtime | format('%0.3f') %] sec
169            
170            
171            
172             [% UNLESS pager.current_page == pager.first_page %]
173            
174             « Prev
175             ·
176            
177             [% IF pager.current_page > (pager.first_page + (self.n / 2) - 1) %]
178            
179             [% pager.first_page %]
180             [% IF pager.current_page > (pager.first_page + (self.n / 2) ) %] ... [% END %]
181            
182             [% END %]
183             [% END %]
184             [% FOR p = pager.pages_in_set %]
185             [% IF p == pager.current_page %]
186             [% p %]
187             [% ELSE %]
188            
189             [% p %]
190            
191             [% END %]
192             [% END %]
193             [% UNLESS pager.current_page == pager.last_page %]
194             [% IF pager.current_page < (pager.last_page - (self.n / 2)) %]
195            
196             [% IF pager.current_page < (pager.last_page - (self.n / 2) - 1) %] ... [% END %]
197             [% pager.last_page %]
198            
199             [% END %]
200            
201             ·
202             Next »
203            
204             [% END %]
205            
206            
207             HTML
208              
209             ############################ simple ##############################
210              
211             $Templates{simple} = <
212             [% count = 0;
213             WHILE (r = results.NextResult);
214             count = count + 1 %]
215             t:[% r.Property('swishtitle') | uri %]
216             u:[% r.Property('swishdocpath') | uri %]
217             r:[% r.Property('swishrank') %]
218             n:[% r.Property('swishreccount') %]
219             -[% LAST IF count == self.p %][% END %]
220             --
221             SIMPLE
222              
223             ############################ RSS ###################################
224              
225             $Templates{rss} = <
226             $XMLDecl
227            
228            
229             [% self.xml.utf8_safe( self.rss.title ) OR self.xml.utf8_safe( self.title ) %]
230             [% self.rss.link || self.uri %]
231             [% self.xml.utf8_safe( self.rss.description ) OR self.xml.utf8_safe( self.description ) %]
232             [% self.rss.lang || 'en-us' %]
233            
234             [% self.xml.utf8_safe( self.rss.img.title ) %]
235             [% self.rss.img.url %]
236             [% self.rss.img.link %]
237            
238             [% count = 0;
239             WHILE (r = results.NextResult);
240             count = count + 1 %]
241            
242             [% self.xml.utf8_safe( r.Property('swishtitle') ) %]
243             [% r.Property('swishdocpath') | uri %]
244             [% self.hiliter.light(
245             self.xml.utf8_safe(
246             self.snipper.snip(
247             r.Property('swishdescription')
248             )
249             )
250             )
251             %]
252            
253             [% LAST IF count == self.p %]
254             [% END %]
255            
256            
257             RSS
258              
259             ################################################################################
260              
261             sub new
262             {
263 1     1 1 14 my $class = shift;
264 1         3 my $self = {};
265 1         3 bless($self, $class);
266              
267 1         22 $self->mk_accessors(
268             qw/
269             error
270             template
271             searchtime
272             query
273             stopwords
274             wordchars
275             beginchars
276             endchars
277             swish
278             templates
279             debug
280             server
281             index
282             uri
283             results
284             title
285             rss
286             hiliter
287             snipper
288             xml
289             /,
290              
291             keys %Defaults
292             );
293              
294 1         1097 return $self->_init(@_);
295             }
296              
297             sub _init
298             {
299 1     1   3 my $self = shift;
300 1         20 $self->{_start} = [gettimeofday()];
301 1 50       5 if (@_ > 1)
    0          
302             {
303 1         4 my %extra = @_;
304 1         4 @$self{keys %extra} = values %extra;
305 1         5 for (keys %Defaults)
306             {
307 6   100     26 $self->{$_} ||= $Defaults{$_};
308             }
309             }
310             elsif (@_)
311             {
312 0         0 for (keys %Defaults)
313             {
314 0 0       0 $self->$_(
315             defined $_[0]->param($_) ? $_[0]->param($_) : $Defaults{$_});
316             }
317             }
318             else
319             {
320 0         0 croak "need name/value pairs or a CGI object";
321             }
322              
323             # enforce max hits
324 1 50       5 if ($self->p > 100)
325             {
326 0         0 $self->p(100);
327             }
328              
329 1 50       9 unless ($self->q)
330             {
331 0         0 $self->error('Query is required at a minimum');
332 0         0 return;
333             }
334             else
335             {
336              
337             # filter query to match swish convention
338             # TODO a filter() callback?
339             # TODO build this =/: opt into swish itself
340 1         9 my $q = $self->q;
341 1         6 $q =~ s,:,=,g;
342 1         4 $self->q($q);
343             }
344              
345             # create template object
346 1 50 33     33 $self->{template} ||= Template->new()
347             or croak $Template::ERROR;
348              
349             # shortcut to S::T::XML
350 1         30132 $self->xml(Search::Tools::XML->new);
351              
352             # defaults
353 1   50     50 $self->{templates} ||= \%Templates;
354 1   33     9 $self->{index} ||= $Index;
355 1   33     11 $self->{server} ||= $Server;
356 1   33     32 $self->{debug} ||= $Debug;
357              
358 1         12 return $self;
359             }
360              
361             sub params
362             {
363 0     0 0 0 my $self = shift;
364              
365             # this is not terribly efficient
366             # but since we can't really cache how else to do it?
367 0         0 return $self->uri . '?'
368 0         0 . join(';', map { $_ . '=' . uri_escape($self->$_) } sort keys %Defaults);
369             }
370              
371             sub page
372             {
373              
374             # set the correct s() value based on $page and ->p()
375             # example: $page = 3 and ->p = 10, so ->s = 31
376 0     0 0 0 my $self = shift;
377 0   0     0 my $page = shift || 1;
378              
379 0         0 $self->s(($page * $self->p) - $self->p + 1);
380              
381             # important to return empty string so template doesn't get value
382 0         0 return '';
383             }
384              
385             sub search
386             {
387 1     1 0 3 my $self = shift;
388              
389 1         6 $self->{_startsearch} = [gettimeofday()];
390              
391 1 50       6 unless ($self->swish)
392             {
393 1         87 eval "require SWISH::API::Remote";
394 1 50       155917 if ($@)
395             {
396 0         0 eval "require SWISH::API";
397              
398 0 0       0 if ($@)
399             {
400 0         0 croak
401             "SWISH::API::Remote or SWISH::API must be installed to use "
402             . ref $self;
403             }
404             else
405             {
406 0         0 $self->swish(SWISH::API->new($self->index));
407             }
408              
409             }
410             else
411             {
412 1         11 $self->swish(
413             SWISH::API::Remote->new(
414             $self->server, $self->index, {DEBUG => $self->debug}
415             )
416             );
417             }
418              
419             }
420              
421 1 50       71 if (ref $self->swish eq 'SWISH::API')
    50          
422             {
423              
424 0         0 my $search = $self->swish->new_search_object;
425 0 0       0 return $self->error if $self->_check_swish_err;
426              
427 0 0       0 $search->set_sort($self->o) if $self->o;
428 0 0       0 return $self->error if $self->_check_swish_err;
429              
430 0         0 $self->results($search->execute($self->q));
431 0 0       0 return $self->error if $self->_check_swish_err;
432              
433 0         0 $self->results->seek_result($self->s - 1);
434 0 0       0 return $self->error if $self->_check_swish_err;
435              
436 0 0       0 if (!%Headers)
437             {
438 0         0 my $i = ($self->swish->index_names)[0];
439 0         0 my @h = $self->swish->header_names;
440 0         0 for (@h)
441             {
442 0         0 $Headers{$_} = join(' ', $self->swish->header_value($i, $_));
443             }
444             }
445              
446             }
447             elsif ($self->swish->isa('SWISH::API::Remote'))
448             {
449              
450             # TODO order ?
451 1         25 $self->results(
452             $self->swish->Execute(
453             $self->q,
454             {
455             BEGIN => $self->s - 1, # swished is 0-based
456             PROPERTIES => $Props,
457             MAX => $self->p
458             }
459             )
460             );
461              
462 1 50       172319 if ($self->results->Error())
463             {
464 1         16 return $self->error($self->results->ErrorString());
465             }
466              
467 0 0       0 if (!%Headers)
468             {
469 0         0 my @h = $self->swish->HeaderList;
470 0         0 for (@h)
471             {
472 0         0 $Headers{$_->Name} = $_->Value;
473             }
474             }
475              
476             }
477              
478 0         0 $self->searchtime(tv_interval($self->{_startsearch}, [gettimeofday()]));
479              
480 0         0 $self->wordchars($Headers{WordCharacters});
481 0         0 $self->beginchars($Headers{BeginCharacters});
482 0         0 $self->endchars($Headers{EndCharacters});
483              
484             # create hiliter and snipper
485 0         0 my $re =
486             Search::Tools->regexp(
487             query => $self->q,
488             word_characters => $self->wordchars,
489             begin_characters => $self->beginchars,
490             end_characters => $self->endchars
491             );
492 0         0 $self->snipper(
493             Search::Tools->snipper(
494             query => $re,
495             context => 12,
496             occur => 2
497             )
498             );
499 0         0 $self->hiliter(Search::Tools->hiliter(query => $re, class => 'hilite'));
500              
501             #carp Dumper \%Headers;
502              
503 0         0 return $self->render;
504              
505             }
506              
507             sub _check_swish_err
508             {
509 0     0   0 my $self = shift;
510 0 0       0 if ($self->swish->critical_error)
511             {
512 0         0 $self->error(
513             $self->swish->error_string . ": " . $self->swish->last_error_msg);
514 0         0 return $self->error;
515             }
516 0         0 return 0;
517             }
518              
519             sub format
520             {
521 0     0 0 0 return $Content_Types{$_[0]->f} . '; charset=utf-8';
522             }
523              
524             sub elapsed
525             {
526 1     1 0 130351 return tv_interval($_[0]->{_start}, [gettimeofday()]);
527             }
528              
529             # the guts
530             sub render
531             {
532 1     1 0 396 my $self = shift;
533 1         7 my $method = lc($self->f);
534              
535 1 50       14 unless (exists $self->templates->{$method})
536             {
537 0         0 $self->error("no Template format for " . $self->f);
538 0         0 return;
539             }
540              
541 1         11 my $response = '';
542              
543 1 50       6 $self->template->process(
544             \$self->templates->{$method},
545             {
546             results => $self->results,
547             pager => $self->pager,
548             self => $self,
549             },
550             \$response
551             )
552             or croak $self->template->error;
553              
554 1         4575 return $response;
555             }
556              
557             sub pager
558             {
559 1     1 0 13 my $self = shift;
560              
561 1 50       5 return $self->{pager} if $self->{pager};
562              
563 1   33     9 my $results = shift || $self->results;
564 1         12 my $this_page = (($self->s - 1) / $self->p) + 1;
565              
566 1   33     20 $self->{pager} ||=
567             Data::Pageset->new(
568             {
569             total_entries => $results->Hits,
570             entries_per_page => $self->p,
571             current_page => $this_page,
572             pages_per_set => $self->n,
573             mode => 'slide',
574             }
575             );
576              
577 1         277 return $self->{pager};
578              
579             }
580              
581             1;
582              
583             __END__