File Coverage

blib/lib/XML/OPDS/Navigation.pm
Criterion Covered Total %
statement 46 46 100.0
branch 5 6 83.3
condition 3 6 50.0
subroutine 12 12 100.0
pod 5 5 100.0
total 71 75 94.6


line stmt bran cond sub pod time code
1             package XML::OPDS::Navigation;
2              
3 5     5   78 use strict;
  5         9  
  5         148  
4 5     5   26 use warnings FATAL => 'all';
  5         10  
  5         249  
5 5     5   25 use Types::Standard qw/Str Enum Bool InstanceOf Object/;
  5         10  
  5         117  
6 5     5   5458 use Moo;
  5         13  
  5         42  
7 5     5   1669 use DateTime;
  5         17  
  5         126  
8 5     5   27 use XML::Atom::Link;
  5         8  
  5         3068  
9              
10             =head1 NAME
11              
12             XML::OPDS::Navigation - Navigation elements for OPDS feeds
13              
14             =head1 SETTERS/ACCESSORS
15              
16             The all are read-write
17              
18             =head2 prefix
19              
20             If provided, every uri will have this string prepended, so you can
21             just pass URIs like '/path/to/file' and have them consistently turned
22             to 'http://myserver.org/path/to/file' if you set this to
23             'http://myserver.org'. See also L<XML::OPDS> C<prefix> method.
24              
25             =head2 href
26              
27             Required. The URI of the resource. If prefix is provided it is
28             prepended on output.
29              
30             =head2 id
31              
32             =head2 rel
33              
34             Defaults to C<subsection>. Permitted values: C<self>, C<start>, C<up>,
35             C<subsection>, C<search>.
36              
37             Additionally:
38              
39             C<new> and C<popular> will expand to L<http://opds-spec.org/sort/new> and
40             L<http://opds-spec.org/sort/popular> as per spec paragraph 7.4.1.
41              
42             Acquisition Feeds using the "http://opds-spec.org/sort/new" relation SHOULD be
43             ordered with the most recent items first. Acquisition Feeds using the "
44             http://opds-spec.org/sort/popular" relation SHOULD be ordered with the most
45             popular items first.
46              
47             C<featured> will expand to L<http://opds-spec.org/featured> as per 7.4.2
48              
49             C<recommended> will expand to L<http://opds-spec.org/recommended> as per 7.4.3
50              
51             C<shelf> will expand to L<http://opds-spec.org/shelf> and
52             C<subscriptions> to L<http://opds-spec.org/subscriptions>.
53              
54             C<crawlable> will expand to L<http://opds-spec.org/crawlable>.
55              
56             This list is a work in progress and probably incomplete.
57              
58             Facets are not supported yet (patches welcome). Client support for
59             facets is unclear. L<https://en.wikipedia.org/wiki/OPDS>.
60              
61             =head2 title
62              
63             =head2 acquistion
64              
65             Boolean, default to false. Indicates that the C<href> is a leaf feed
66             with acquisition entries.
67              
68             =head2 description
69              
70             HTML allowed.
71              
72             =head2 updated
73              
74             A L<DateTime> object with the time of last update.
75              
76             =head2 prefix
77              
78              
79              
80             =cut
81              
82             has id => (is => 'rw', isa => Str);
83              
84             has rel => (is => 'rw',
85             isa => Enum[qw/self start up subsection search/,
86             qw/first last previous next/, # RFC 5005
87             keys(%{ +{ __PACKAGE__->_rel_map } })],
88             default => sub { 'subsection' });
89              
90             has title => (is => 'rw', isa => Str);
91              
92             has href => (is => 'rw', isa => Str, required => 1);
93              
94             has acquisition => (is => 'rw', isa => Bool, default => sub { 0 });
95              
96             has description => (is => 'rw', isa => Str);
97              
98             has updated => (is => 'rw', isa => InstanceOf['DateTime'],
99             default => sub { return DateTime->now });
100              
101             has prefix => (is => 'rw', isa => Str, default => sub { '' });
102              
103             has _dt_formatter => (is => 'ro', isa => Object, default => sub { DateTime::Format::RFC3339->new });
104              
105             =head1 METHODS
106              
107             The are mostly internals and used by L<XML::OPDS>
108              
109             =head2 link_type
110              
111             Depend if C<acquisition> is true of false.
112              
113             =head2 as_link
114              
115             The navigation as L<XML::Atom::Link> object.
116              
117             =head2 identifier
118              
119             Return the id or the URI.
120              
121             =head2 relationship
122              
123             [INTERNAL] Resolve the rel shortcuts.
124              
125             =head2 as_entry
126              
127             The navigation as L<XML::Atom::Entry> object.
128              
129             =cut
130              
131             sub link_type {
132 50     50 1 73 my $self = shift;
133 50 100       753 if ($self->rel eq 'search') {
134 3         20 return "application/opensearchdescription+xml";
135             }
136 47 100       960 my $kind = $self->acquisition ? 'acquisition' : 'navigation';
137 47         368 return "application/atom+xml;profile=opds-catalog;kind=$kind";
138             }
139              
140             sub _rel_map {
141 55     55   461 my %map = (
142             new => "http://opds-spec.org/sort/new",
143             popular => "http://opds-spec.org/sort/popular",
144             featured => "http://opds-spec.org/featured",
145             recommended => "http://opds-spec.org/recommended",
146             shelf => "http://opds-spec.org/shelf",
147             subscriptions => "http://opds-spec.org/subscriptions",
148             crawlable => "http://opds-spec.org/crawlable",
149             );
150             }
151              
152             sub relationship {
153 50     50 1 74 my $self = shift;
154 50         100 my %mapped = $self->_rel_map;
155 50         992 my $rel = $self->rel;
156 50   66     524 return $mapped{$rel} || $rel;
157             }
158              
159             sub as_link {
160 50     50 1 82 my $self = shift;
161 50         139 my $link = XML::Atom::Link->new(Version => 1.0);
162 50         3013 $link->rel($self->relationship);
163 50         1792 $link->href($self->prefix . $self->href);
164 50         1900 $link->type($self->link_type);
165 50 50       1566 if (my $title = $self->title) {
166 50         342 $link->title($title);
167             }
168 50         981 return $link;
169             }
170              
171             sub identifier {
172 22     22 1 40 my $self = shift;
173 22   33     374 return $self->id || $self->prefix . $self->href;
174             }
175              
176             sub as_entry {
177 3     3 1 16 my $self = shift;
178 3         22 my $item = XML::Atom::Entry->new(Version => 1.0);
179 3         368 $item->title($self->title);
180 3         271 $item->id($self->identifier);
181 3         433 $item->content($self->description);
182 3         2561 $item->updated($self->_dt_formatter->format_datetime($self->updated));
183 3         789 $item->add_link($self->as_link);
184 3         83 return $item;
185             }
186              
187             1;