File Coverage

blib/lib/Plack/App/SeeAlso.pm
Criterion Covered Total %
statement 135 138 97.8
branch 21 28 75.0
condition 12 27 44.4
subroutine 31 31 100.0
pod 5 6 83.3
total 204 230 88.7


line stmt bran cond sub pod time code
1 3     3   158562 use strict;
  3         5  
  3         1296  
2 3     3   22 use warnings;
  3         5  
  3         158  
3             package Plack::App::SeeAlso;
4             #ABSTRACT: SeeAlso Server as PSGI application
5             $Plack::App::SeeAlso::VERSION = '0.14';
6 3     3   16 use feature ':5.10';
  3         11  
  3         384  
7              
8 3     3   2535 use Plack::Request;
  3         185844  
  3         105  
9 3     3   2650 use Plack::Middleware::JSONP;
  3         19434  
  3         92  
10 3     3   2605 use Plack::Middleware::Static;
  3         32109  
  3         128  
11 3     3   3731 use Plack::App::unAPI qw(0.3);
  3         6676  
  3         636  
12 3     3   2915 use File::ShareDir qw(dist_dir);
  3         26218  
  3         305  
13 3     3   27 use Plack::Util;
  3         7  
  3         72  
14 3     3   32 use Carp qw(croak);
  3         5  
  3         188  
15 3     3   14 use Scalar::Util qw(blessed reftype);
  3         5  
  3         134  
16 3     3   2935 use Try::Tiny;
  3         4452  
  3         160  
17 3     3   3278 use JSON;
  3         51739  
  3         18  
18 3     3   3631 use Encode;
  3         38092  
  3         288  
19              
20 3     3   29 use parent 'Plack::Component';
  3         5  
  3         25  
21 3     3   182 use parent 'Exporter';
  3         6  
  3         12  
22              
23 3     3   1912 use SeeAlso::Format;
  3         45  
  3         324  
24              
25             our @EXPORT = qw(push_seealso);
26             our @EXPORT_OK = qw(valid_seealso);
27              
28             # properties of the server form OpenSearch Description
29 3     3   89 our @PROPERTIES; BEGIN { @PROPERTIES = qw(Query Stylesheet Formats Examples
30             ShortName LongName Attribution Tags Contact Description Source
31             DateModified Developer); }
32              
33 3     3   18 use Plack::Util::Accessor (@PROPERTIES, 'base');
  3         6  
  3         34  
34              
35             # browsers will more likely complain otherwise
36 3     3   456 use Plack::MIME;
  3         6  
  3         307  
37             Plack::MIME->add_type( '.xsl' => 'text/xsl' );
38              
39             sub prepare_app {
40 6     6 1 71861 my $self = shift;
41 6 100       40 return if $self->{app}; # already initialized
42              
43             # get default configuration from module variables
44 3 100       17 $self->{Stylesheet} = 'seealso.xsl' unless exists $self->{Stylesheet};
45 3         13 foreach (@PROPERTIES) {
46 3     3   18 no strict 'refs';
  3         6  
  3         4175  
47 39 100       103 unless (exists $self->{$_}) {
48 33   100     33 $self->{$_} = ${ref($self)."::$_"} // '';
  33         244  
49             }
50             }
51              
52             # validate and normalize configuration
53 3   50     27 $self->{ShortName} = sprintf '%.16s', $self->{ShortName} // '';
54 3   50     17 $self->{LongName} = sprintf '%.48s', $self->{LongName} // '' ;
55 3   50     18 $self->{Description} = sprintf '%.1024s', $self->{Description} // '';
56 3   50     21 $self->{Tags} = sprintf '%.256s', $self->{Tags} // '';
57 3   50     15 $self->{Attribution} = sprintf '%.256s', $self->{Attribution} // '';
58              
59 3         9 my $examples = $self->{Examples};
60 3 50 33     24 $examples = [] unless ref $examples and reftype $examples eq 'ARRAY';
61 0 0 0     0 $self->{Examples} = [
62 3         11 grep { ref $_ and reftype($_) eq 'HASH' and $_->{id} } @$examples
63             ];
64              
65             # TODO: validate
66             # Stylesheet
67             # Formats
68             # Contact
69             # Source
70             # DateModified
71              
72 3 50       5 my %formats = %{ $self->{Formats} || { } };
  3         22  
73 3         19 delete $formats{$_} for (qw(opensearchdescription seealso _));
74              
75             # TODO: extend known formats: csv, redirect
76             # my $f = SeeAlso::Format->new( $_ )
77             # seealso => [ $f->app => $f->type ]
78 3         30 my $f = SeeAlso::Format->new('seealso');
79             #my $f = SeeAlso::Format::seealso->new;#('seealso');
80              
81             # never return format list if format parameter given
82 3         14 $formats{_} = { always => 1 };
83             $formats{opensearchdescription} = [
84 3     1   18 sub { $self->openSearchDescription(@_); } => 'application/opensearchdescription+xml',
  1         485  
85             ];
86 3     5   44 $formats{seealso} = [ $f->app( sub { $self->query(@_) } ), $f->type ];
  5         30  
87              
88 3         21 my $app = unAPI( %formats );
89 3         546 $app = Plack::Middleware::JSONP->wrap($app);
90              
91 3 100       362 if ($self->{Stylesheet}) {
92 2         15 $app = Plack::Middleware::Static->wrap( $app,
93             path => qw{seealso\.(js|xsl|css)$},
94             root => dist_dir('Plack-App-SeeAlso')
95             );
96             }
97              
98 3         510 $self->{app} = $app;
99             }
100              
101             sub query {
102 5     5 1 11 my ($self, $id) = @_;
103 5 50       31 return ( $self->{Query} ? $self->{Query}->( $id ) : [$id,[]] );
104             }
105              
106             sub call {
107 9     9 1 5561 my ($self, $env) = @_;
108              
109 9         34 my $result = $self->{app}->( $env );
110              
111             Plack::Util::response_cb( $result, sub {
112 5     5   42 my $res = shift;
113 5 100       22 return unless $res->[0] == 300;
114 2   33     16 my $base = $self->{base} || Plack::Request->new($env)->base;
115 2         335 my $xsl = $self->{Stylesheet};
116 2         6 $xsl = '';
117 2         9 $xsl .= "\n\n";
118 2         32 $res->[2]->[0] =~ s{\?>\s+\n$xsl
119 9 100       2534 } ) if $self->{Stylesheet};
120              
121 9         132 return $result;
122             }
123              
124             sub openSearchDescription {
125 1     1 0 3 my ($self, $env) = @_;
126 1         4 my $base = Plack::Request->new($env)->base;
127              
128 1         197 my @xml = '
129            
130             xmlns:dc="http://purl.org/dc/elements/1.1/"
131             xmlns:dcterms="http://purl.org/dc/terms/"
132             xmlns:seealso="http://ws.gbv.de/seealso/schema/">';
133              
134 11         20 my @prop = (
135 1         3 map { $_ => $_ } qw(ShortName LongName Description Tags Contact Developer Attribution),
136             DateModified => 'dcterms:modified',
137             Source => 'dc:source',
138             );
139 1         6 while (@prop) {
140 11         13 my $field = shift @prop;
141 11         14 my $tag = shift @prop;
142 11 100       35 my $value = $self->{$field} or next;
143 3         10 push @xml, " <$tag>"._xmlescape($value)."";
144             }
145              
146 1 50       2 foreach (@{ $self->{Examples} || [] }) {
  1         6  
147 0         0 my $id = _xmlescape($_->{id});
148 0         0 push @xml, "";
149             }
150              
151 1 50       6 my $tpl = $base . ($base =~ /\?/ ? '&' : '?')
152             . "id={searchTerms}&format=seealso&callback={callback}";
153 1         15 push @xml, " ";
154              
155 1         4 push @xml, '','';
156              
157 1         9 return [ 200, [ "Content-Type"
158             => 'application/opensearchdescription+xml; charset: utf-8' ],
159             [ encode('utf8', join "\n", @xml) ]
160             ];
161             }
162              
163             sub push_seealso ($$$$) {
164 2     2 1 31 my $resp = shift;
165 2   50     51 push @{$resp->[1]}, (shift // '');
  2         13  
166 2   50     3 push @{$resp->[2]}, (shift // '');
  2         16  
167 2   50     4 push @{$resp->[3]}, (shift // '');
  2         10  
168 2         10 $resp;
169             }
170              
171             sub valid_seealso ($) {
172 5     5 1 1373 return SeeAlso::Format::valid(@_);
173             }
174              
175             # Replace &, <, >, " by XML entities.
176             sub _xmlescape {
177 4     4   5 my $xml = shift;
178 4 100       12 if ($xml =~ /[\&\<\>"]/) {
179 1         12 $xml =~ s/\&/\&\;/g;
180 1         2 $xml =~ s/\
181 1         3 $xml =~ s/\>/\>\;/g;
182 1         2 $xml =~ s/"/\"\;/g;
183             }
184 4         18 return $xml;
185             }
186              
187             1;
188              
189             __END__