File Coverage

blib/lib/WWW/NOS/Open.pm
Criterion Covered Total %
statement 113 207 54.5
branch 10 38 26.3
condition 1 8 12.5
subroutine 34 46 73.9
pod 7 7 100.0
total 165 306 53.9


line stmt bran cond sub pod time code
1             package WWW::NOS::Open 0.101; # -*- cperl; cperl-indent-level: 4 -*-
2 5     5   226912 use strict;
  5         12  
  5         119  
3 5     5   25 use warnings;
  5         9  
  5         119  
4              
5 5     5   2489 use utf8;
  5         64  
  5         24  
6 5     5   237 use 5.014000;
  5         15  
7              
8 5     5   1658 use Date::Calc qw(Add_Delta_Days Date_to_Days Delta_Days Today);
  5         24514  
  5         335  
9 5     5   1829 use Date::Format;
  5         25508  
  5         254  
10 5     5   2013 use HTTP::Headers;
  5         27504  
  5         151  
11 5     5   1693 use HTTP::Request;
  5         44815  
  5         171  
12             use HTTP::Status
13 5     5   1796 qw(HTTP_OK HTTP_BAD_REQUEST HTTP_UNAUTHORIZED HTTP_FORBIDDEN HTTP_INTERNAL_SERVER_ERROR);
  5         14706  
  5         532  
14 5     5   2069 use JSON;
  5         32680  
  5         24  
15 5     5   2940 use LWP::UserAgent;
  5         63523  
  5         167  
16 5     5   3213 use Log::Log4perl qw(:easy get_logger);
  5         169781  
  5         31  
17 5     5   5637 use Moose qw/around has with/;
  5         1923602  
  5         36  
18 5     5   28354 use Moose::Util::TypeConstraints qw/enum/;
  5         13  
  5         46  
19 5     5   2050 use URI::Escape qw(uri_escape);
  5         10  
  5         322  
20 5     5   25 use URI;
  5         11  
  5         105  
21 5     5   4025 use XML::Simple;
  5         31324  
  5         35  
22              
23 5     5   2418 use namespace::autoclean '-also' => qr/^__/sxm;
  5         30375  
  5         54  
24              
25 5     5   2156 use WWW::NOS::Open::Article;
  5         20  
  5         192  
26 5     5   2435 use WWW::NOS::Open::AudioFragment;
  5         22  
  5         190  
27 5     5   2418 use WWW::NOS::Open::Broadcast;
  5         24  
  5         191  
28 5     5   2386 use WWW::NOS::Open::DayGuide;
  5         19  
  5         208  
29 5     5   2432 use WWW::NOS::Open::Document;
  5         25  
  5         211  
30 5     5   2422 use WWW::NOS::Open::Exceptions;
  5         15  
  5         125  
31 5     5   1480 use WWW::NOS::Open::Result;
  5         25  
  5         228  
32 5     5   59 use WWW::NOS::Open::TypeDef;
  5         12  
  5         51  
33 5     5   7182 use WWW::NOS::Open::Version;
  5         20  
  5         180  
34 5     5   2331 use WWW::NOS::Open::Video;
  5         21  
  5         189  
35              
36 5     5   41 use Readonly;
  5         10  
  5         10033  
37             Readonly::Scalar my $SERVER => $ENV{'NOSOPEN_SERVER'} || q{http://open.nos.nl};
38             Readonly::Scalar my $TIMEOUT => 15;
39             Readonly::Scalar my $AGENT => q{WWW::NOS::Open/} . $WWW::NOS::Open::VERSION;
40             Readonly::Scalar my $DATE_FORMAT => q{%04u-%02u-%02u};
41             Readonly::Scalar my $DEFAULT_START => -1; # Yesterday
42             Readonly::Scalar my $DEFAULT_END => 1; # Tomorrow
43             Readonly::Scalar my $MAX_RANGE => 14; # Two weeks
44             Readonly::Scalar my $GET => q{GET};
45             Readonly::Scalar my $DEFAULT_API_KEY => q{TEST};
46             Readonly::Scalar my $DEFAULT_OUTPUT => q{xml};
47             Readonly::Scalar my $DEFAULT_CATEGORY => q{nieuws};
48             Readonly::Scalar my $DASH => q{-};
49             Readonly::Scalar my $DOUBLE_COLON => q{::};
50             Readonly::Scalar my $FRAGMENT => q{Fragment};
51             Readonly::Scalar my $VERSION_PATH => q{%s/v1/index/version/key/%s/output/%s/};
52             Readonly::Scalar my $LATEST_PATH =>
53             q{%s/v1/latest/%s/key/%s/output/%s/category/%s/};
54             Readonly::Scalar my $SEARCH_PATH => q{%s/v1/search/query/key/%s/output/%s/q/%s};
55             Readonly::Scalar my $GUIDE_PATH =>
56             q{%s/v1/guide/%s/key/%s/output/%s/start/%s/end/%s/};
57             Readonly::Scalar my $XML_DETECT => qr{^<}smx;
58             Readonly::Scalar my $STRIP_PRIVATE => qr{^_}smx;
59              
60             Readonly::Hash my %ERR => (
61             'INTERNAL_SERVER' => q{Internal server error or no response recieved},
62             'UNPARSABLE' => q{Could not parse data},
63             'EXCEEDED_RANGE' => qq{Date range exceeds maximum of $MAX_RANGE days},
64             );
65             Readonly::Hash my %LOG => (
66             'REQUESTING' => q{Requesting %s},
67             'RESPONSE_CODE' => q{Response code %d},
68             );
69              
70             Log::Log4perl::easy_init($ERROR);
71              
72             my $log = Log::Log4perl->get_logger(__PACKAGE__);
73              
74             has '_ua' => (
75             'is' => 'ro',
76             'isa' => 'LWP::UserAgent',
77             'default' => sub {
78             LWP::UserAgent->new(
79             'timeout' => $TIMEOUT,
80             'agent' => $AGENT,
81             );
82             },
83             );
84              
85             has '_version' => (
86             'is' => 'ro',
87             'isa' => 'WWW::NOS::Open::Version',
88             );
89              
90             sub get_version {
91 3     3 1 1181 my $self = shift;
92 3         96 my $url = sprintf $VERSION_PATH, $SERVER,
93             URI::Escape::uri_escape( $self->get_api_key ),
94             URI::Escape::uri_escape( $self->_get_default_output );
95 3         55 my $response = $self->_do_request($url);
96 0         0 my $version = $self->_parse_version( $response->decoded_content );
97 0         0 return $version;
98             }
99              
100             sub _parse_version {
101 0     0   0 my ( $self, $body ) = @_;
102 0         0 my ( $version, $build );
103 0 0       0 if ( $body =~ /$XML_DETECT/gsmx ) {
104 0         0 my $xml = XML::Simple->new( 'ForceArray' => 1 )->XMLin($body);
105 0         0 $version = $xml->{'item'}[0]->{'version'}[0];
106 0         0 $build = $xml->{'item'}[0]->{'build'}[0];
107             }
108             else {
109 0         0 $log->fatal( $ERR{'UNPARSABLE'} );
110             }
111 0         0 return WWW::NOS::Open::Version->new( $version, $build );
112             }
113              
114             has '_default_output' => (
115             'is' => 'ro',
116             'isa' => 'Str',
117             'default' => $DEFAULT_OUTPUT,
118             'reader' => '_get_default_output',
119             'init_arg' => 'default_output',
120             );
121              
122             has '_api_key' => (
123             'is' => 'rw',
124             'isa' => 'Str',
125             'default' => $DEFAULT_API_KEY,
126             'reader' => 'get_api_key',
127             'writer' => 'set_api_key',
128             'init_arg' => 'api_key',
129             );
130              
131             sub _get_latest_resources {
132 0     0   0 my ( $self, $type, $category ) = @_;
133 0 0       0 ( defined $category ) || ( $category = $DEFAULT_CATEGORY );
134 0         0 my $url = sprintf $LATEST_PATH,
135             $SERVER,
136             URI::Escape::uri_escape($type),
137             URI::Escape::uri_escape( $self->get_api_key ),
138             URI::Escape::uri_escape( $self->_get_default_output ),
139             URI::Escape::uri_escape($category);
140 0         0 my $response = $self->_do_request($url);
141 0         0 my @resources =
142             $self->_parse_resources( $type, $response->decoded_content );
143 0         0 return @resources;
144             }
145              
146             sub get_latest_articles {
147 0     0 1 0 my ( $self, @param ) = @_;
148 0         0 return $self->_get_latest_resources( q{article}, @param );
149             }
150              
151             sub __get_props {
152             my $meta = shift;
153             my @props = map { $_->name } $meta->get_all_attributes;
154             for (@props) {
155             s/$STRIP_PRIVATE//smx;
156             }
157             return @props;
158             }
159              
160             sub _parse_resource {
161 0     0   0 my ( $self, $type, $hr_resource ) = @_;
162 0         0 my %mapping = (
163             'article' => __PACKAGE__ . $DOUBLE_COLON . ucfirst $type,
164             'video' => __PACKAGE__ . $DOUBLE_COLON . ucfirst $type,
165             'audio' => __PACKAGE__ . $DOUBLE_COLON . ucfirst $type . $FRAGMENT,
166             'document' => __PACKAGE__ . $DOUBLE_COLON . ucfirst $type,
167             'broadcast' => __PACKAGE__ . $DOUBLE_COLON . ucfirst $type,
168             );
169              
170 0         0 my @props = __get_props( ( $mapping{$type} )->meta );
171 0         0 my %param;
172 0         0 while ( my $prop = shift @props ) {
173             $param{$prop} =
174             ( q{HASH} eq ref $hr_resource->{$prop}[0] )
175 0         0 ? %{ $hr_resource->{$prop}[0] }
176 0 0       0 : $hr_resource->{$prop}[0];
177             }
178 0   0     0 $param{'keywords'} = $hr_resource->{'keywords'}->[0]->{'keyword'} || [];
179 0 0       0 if ( my $resource = ( $mapping{$type} )->new(%param) ) {
180 0         0 return $resource;
181             }
182 0         0 return;
183             }
184              
185             sub _parse_resources {
186 0     0   0 my ( $self, $type, $body ) = @_;
187 0         0 my @resources;
188              
189 0 0       0 if ( $body =~ /$XML_DETECT/gsmx ) {
190 0         0 my $xml = XML::Simple->new( 'ForceArray' => 1 )->XMLin($body);
191 0         0 my @xml_resources = @{ $xml->{$type} };
  0         0  
192 0         0 while ( my $resource = shift @xml_resources ) {
193 0         0 push @resources, $self->_parse_resource( $type, $resource );
194             }
195 0         0 return @resources;
196             }
197             else {
198 0         0 $log->fatal( $ERR{'UNPARSABLE'} );
199             }
200 0         0 return ();
201             }
202              
203             sub get_latest_videos {
204 0     0 1 0 my ( $self, @param ) = @_;
205 0         0 return $self->_get_latest_resources( q{video}, @param );
206             }
207              
208             sub get_latest_audio_fragments {
209 0     0 1 0 my ( $self, @param ) = @_;
210 0         0 return $self->_get_latest_resources( q{audio}, @param );
211             }
212              
213             sub _parse_result {
214 0     0   0 my ( $self, $body ) = @_;
215 0         0 my @documents;
216 0 0       0 if ( $body =~ /$XML_DETECT/gsmx ) {
217 0         0 my $xml = XML::Simple->new( 'ForceArray' => 1 )->XMLin($body);
218 0         0 my @xml_documents = @{ $xml->{'documents'}->[0]->{'document'} };
  0         0  
219 0         0 while ( my $hr_document = shift @xml_documents ) {
220 0         0 push @documents,
221             $self->_parse_resource( q{document}, $hr_document );
222             }
223             my $result = WWW::NOS::Open::Result->new(
224             'documents' => [@documents],
225 0         0 'related' => $xml->{'related'}->[0]->{'related'},
226             );
227 0         0 return $result;
228             }
229             else {
230 0         0 $log->fatal( $ERR{'UNPARSABLE'} );
231             }
232 0         0 return ();
233             }
234              
235             sub search {
236 0     0 1 0 my ( $self, $query ) = @_;
237 0         0 my $url = sprintf $SEARCH_PATH,
238             $SERVER,
239             URI::Escape::uri_escape( $self->get_api_key ),
240             URI::Escape::uri_escape( $self->_get_default_output ),
241             URI::Escape::uri_escape($query);
242 0         0 my $response = $self->_do_request($url);
243 0         0 my $result = $self->_parse_result( $response->decoded_content );
244 0         0 return $result;
245             }
246              
247             sub __get_date {
248             my ( $start_day, $end_day ) = @_;
249             my $today = Date_to_Days(Today);
250             return (
251             (
252             sprintf $DATE_FORMAT,
253             Add_Delta_Days( 1, 1, 1, $today + $start_day - 1 ),
254             ),
255             (
256             sprintf $DATE_FORMAT,
257             Add_Delta_Days( 1, 1, 1, $today + $end_day - 1 ),
258             ),
259             );
260             }
261              
262             sub _parse_dayguide {
263 0     0   0 my ( $self, $hr_dayguide ) = @_;
264              
265 0         0 my @props = __get_props( WWW::NOS::Open::DayGuide->meta );
266 0         0 my %param;
267 0         0 while ( my $prop = shift @props ) {
268             $param{$prop} =
269             ( q{ARRAY} eq ref $hr_dayguide->{$prop} )
270             ? (
271             ( q{HASH} eq ref $hr_dayguide->{$prop}[0] )
272 0         0 ? %{ $hr_dayguide->{$prop}[0] }
273             : $hr_dayguide->{$prop}[0]
274             )
275 0 0       0 : $hr_dayguide->{$prop};
    0          
276             }
277 0         0 $param{'broadcasts'} = [];
278 0         0 my @broadcasts = $hr_dayguide->{'item'};
279 0         0 while ( my $ar_broadcast = shift @broadcasts ) {
280 0         0 push @{ $param{'broadcasts'} },
  0         0  
281             $self->_parse_resource( q{broadcast}, $ar_broadcast->[0] );
282             }
283 0 0       0 if ( my $dayguide = WWW::NOS::Open::DayGuide->new(%param) ) {
284 0         0 return $dayguide;
285             }
286 0         0 return;
287             }
288              
289             sub _parse_guide {
290 0     0   0 my ( $self, $body ) = @_;
291 0         0 my @dayguides;
292 0 0       0 if ( $body =~ /$XML_DETECT/gsmx ) {
293 0         0 my $xml = XML::Simple->new( 'ForceArray' => 1 )->XMLin($body);
294 0         0 my @xml_dayguides = @{ $xml->{'dayguide'} };
  0         0  
295 0         0 while ( my $hr_dayguide = shift @xml_dayguides ) {
296 0         0 push @dayguides, $self->_parse_dayguide($hr_dayguide);
297             }
298 0         0 return @dayguides;
299             }
300             else {
301 0         0 $log->fatal( $ERR{'UNPARSABLE'} );
302             }
303 0         0 return ();
304             }
305              
306             sub _get_broadcasts {
307 4     4   14 my ( $self, $type, $start, $end, $channel ) = @_;
308              
309 4         17 my ( $default_start, $default_end ) =
310             __get_date( $DEFAULT_START, $DEFAULT_END );
311 4 50       17 ( defined $start ) || ( $start = $default_start );
312 4 50       13 ( defined $end ) || ( $end = $default_end );
313              
314 4         11 foreach ( $start, $end ) {
315 8 100       65 ( q{DateTime} eq ref ) && ( $_ = $_->ymd );
316             }
317 4 100       71 if ( Delta_Days( split /$DASH/smx, qq{$start$DASH$end} ) > $MAX_RANGE ) {
318             ## no critic qw(RequireExplicitInclusion)
319             NOSOpenExceededRangeException->throw(
320             ## use critic
321 2         17 'error' => $ERR{'EXCEEDED_RANGE'},
322             );
323             }
324 2         13 my $url = sprintf $GUIDE_PATH,
325             $SERVER,
326             URI::Escape::uri_escape($type),
327             URI::Escape::uri_escape( $self->get_api_key ),
328             URI::Escape::uri_escape( $self->_get_default_output ),
329             URI::Escape::uri_escape($start),
330             URI::Escape::uri_escape($end);
331 2         58 my $response = $self->_do_request($url);
332 0         0 my @guide_days = $self->_parse_guide( $response->decoded_content );
333 0         0 return @guide_days;
334             }
335              
336             sub get_tv_broadcasts {
337 0     0 1 0 my ( $self, @param ) = @_;
338 0         0 return $self->_get_broadcasts( q{tv}, @param );
339             }
340              
341             sub get_radio_broadcasts {
342 4     4 1 17646 my ( $self, @param ) = @_;
343 4         13 return $self->_get_broadcasts( q{radio}, @param );
344             }
345              
346             sub _do_request {
347 5     5   15 my ( $self, $url ) = @_;
348 5         32 my $request = HTTP::Request->new(
349             $GET => $url,
350             HTTP::Headers->new(),
351             );
352 5         17705 $log->debug( sprintf $LOG{'REQUESTING'}, $url );
353 5         294 my $response = $self->_ua->request($request);
354 5         761764 $log->debug( sprintf $LOG{'RESPONSE_CODE'}, $response->code );
355 5 50       204 if ( $response->code == HTTP_INTERNAL_SERVER_ERROR ) {
    50          
356             ## no critic qw(RequireExplicitInclusion)
357             NOSOpenInternalServerErrorException->throw(
358             ## use critic
359 0         0 'error' => $ERR{'INTERNAL_SERVER'},
360             );
361             }
362             elsif ( $response->code > HTTP_OK ) {
363 5         190 my $json = JSON->new;
364 5 50       20 if ( $response->code == HTTP_BAD_REQUEST ) {
    50          
    0          
365             ## no critic qw(RequireExplicitInclusion)
366 0         0 NOSOpenBadRequestException->throw(
367             ## use critic
368             'error' => $json->decode( $response->decoded_content ),
369             );
370             }
371             elsif ( $response->code == HTTP_UNAUTHORIZED ) {
372             ## no critic qw(RequireExplicitInclusion)
373 5   33     185 NOSOpenUnauthorizedException->throw(
374             ## use critic
375             'error' => $json->decode(
376             $response->decoded_content || $response->content,
377             ),
378             );
379             }
380             elsif ( $response->code == HTTP_FORBIDDEN ) {
381             ## no critic qw(RequireExplicitInclusion)
382 0   0       NOSOpenForbiddenException->throw(
383             ## use critic
384             'error' => $json->decode(
385             $response->decoded_content || $response->content,
386             ),
387             );
388             }
389             }
390 0           return $response;
391             }
392              
393             around 'BUILDARGS' => sub {
394             my $orig = shift;
395             my $class = shift;
396             my ( $api_key, $default_output ) = @_;
397              
398             return $class->$orig(
399             'api_key' => $api_key || $DEFAULT_API_KEY,
400             'default_output' => $default_output || $DEFAULT_OUTPUT,
401             );
402             };
403              
404             with 'WWW::NOS::Open::Interface';
405              
406 5     5   45 no Moose;
  5         13  
  5         35  
407              
408             __PACKAGE__->meta->make_immutable;
409              
410             1;
411              
412             __END__
413              
414             =encoding utf8
415              
416             =for stopwords DateTime perl JSON Readonly URI PHP Ipenburg MERCHANTABILITY
417              
418             =head1 NAME
419              
420             WWW::NOS::Open - Perl framework for the Open NOS REST API.
421              
422             =head1 VERSION
423              
424             This document describes WWW::NOS::Open version 0.101.
425              
426             =head1 SYNOPSIS
427              
428             use WWW::NOS::Open;
429             my $nos = WWW::NOS::Open->new($API_KEY);
430             @latest_articles = $nos->get_latest_articles('nieuws');
431              
432             =head1 DESCRIPTION
433              
434             The L<Dutch public broadcasting foundation NOS|http:://www.nos.nl> provides a
435             REST API to their content. This module provides a wrapper around that API to
436             use data from the Open NOS platform with Perl.
437              
438             =head1 SUBROUTINES/METHODS
439              
440             =head2 C<new>
441              
442             Create a new WWW::NOS::Open object.
443              
444             =over
445              
446             =item 1. The API key to use in the connection to the Open NOS service. You
447             need to L<register at Open NOS|http://open.nos.nl/registratie/> to get an API
448             key, and link your IP address to that account to do authorized requests.
449              
450             =back
451              
452             =head2 C<get_version>
453              
454             Gets the version of the REST API as a
455             L<WWW::NOS::Open::Version|WWW::NOS::Open::Version> object.
456              
457             =head2 C<get_latest_articles>
458              
459             Returns the ten most recent articles as an array of
460             L<WWW::NOS::Open::Article|WWW::NOS::Open::Article> objects.
461              
462             =over
463              
464             =item 1. The optional category of the requested articles, C<nieuws> or
465             C<sport>. Defaults to the category C<nieuws>.
466              
467             =back
468              
469             =head2 C<get_latest_videos>
470              
471             Returns the ten most recent videos as an array of
472             L<WWW::NOS::Open::Video|WWW::NOS::Open::Video> objects.
473              
474             =over
475              
476             =item 1. The optional category of the requested videos, C<nieuws> or C<sport>.
477             Defaults to the category C<nieuws>.
478              
479             =back
480              
481             =head2 C<get_latest_audio_fragments>
482              
483             Returns the ten most recent audio fragments as an array of
484             L<WWW::NOS::Open::AudioFragment|WWW::NOS::Open::AudioFragment> objects.
485              
486             =over
487              
488             =item 1. The optional category of the requested audio fragments, C<nieuws> or
489             C<sport>. Defaults to the category C<nieuws>.
490              
491             =back
492              
493             =head2 C<search>
494              
495             Search the search engine from L<NOS|http://www.nos.nl> for keywords. Returns
496             a L<WWW::NOS::Open::Results|WWW::NOS::Open::Results> object with a maximum of
497             25 items.
498              
499             =over
500              
501             =item 1. The keyword or a combination of keywords, for example C<cricket>,
502             C<cricket AND engeland>, C<cricket OR curling>.
503              
504             =back
505              
506             =head2 C<get_tv_broadcasts>
507              
508             Gets a collection of television broadcasts between two optional dates. Returns
509             an array of L<WWW::NOS::Open::DayGuide|WWW::NOS::Open::DayGuide> objects. The
510             period defaults to starting yesterday and ending tomorrow. The period has an
511             upper limit of 14 days. An C<NOSOpenExceededRangeException> is thrown when
512             this limit is exceeded.
513              
514             =over
515              
516             =item 1. Start date in the format C<YYYY-MM-DD> or as L<DateTime|DateTime>
517             object.
518              
519             =item 2. End date in the format C<YYYY-MM-DD> or as L<DateTime|DateTime>
520             object.
521              
522             =back
523              
524             =head2 C<get_radio_broadcasts>
525              
526             Gets a collection of radio broadcasts between two optional dates. Returns an
527             array of L<WWW::NOS::Open::DayGuide|WWW::NOS::Open::DayGuide> objects. The
528             period defaults to starting yesterday and ending tomorrow. The period has an
529             upper limit of 14 days. An C<NOSOpenExceededRangeException> is thrown when this
530             limit is exceeded.
531              
532             =over
533              
534             =item 1. Start date in the format C<YYYY-MM-DD> or as L<DateTime|DateTime>
535             object.
536              
537             =item 2. End date in the format C<YYYY-MM-DD> or as L<DateTime|DateTime>
538             object.
539              
540             =back
541              
542             =head1 CONFIGURATION AND ENVIRONMENT
543              
544             To use this module with the live content of Open NOS you need an API key which
545             can be obtained by registering at L<Open NOS|http://open.nos.nl/registratie/>
546             and then configure your account there with the IP range you'll be accessing
547             the service from.
548              
549             This module can use the optional environment variable C<NOSOPEN_SERVER> to
550             specify a server URL that is not the default Open NOS live service at
551             L<http://open.nos.nl|http://open.nos.nl>.
552              
553             The user agent identifier used in the request to the REST API is
554             C<WWW::NOS::Open/0.101>.
555              
556             =head1 DEPENDENCIES
557              
558             =over
559              
560             =item * perl 5.14
561              
562             =item * L<Date::Calc|Date::Calc>
563              
564             =item * L<Date::Format|Date::Format>
565              
566             =item * L<HTTP::Headers|HTTP::Headers>
567              
568             =item * L<HTTP::Request|HTTP::Request>
569              
570             =item * L<HTTP::Status|HTTP::Status>
571              
572             =item * L<JSON|JSON>
573              
574             =item * L<LWP::UserAgent|LWP::UserAgent>
575              
576             =item * L<Log::Log4perl|Log::Log4perl>
577              
578             =item * L<Moose|Moose>
579              
580             =item * L<Moose::Util::TypeConstraints|Moose::Util::TypeConstraints>
581              
582             =item * L<Readonly|Readonly>
583              
584             =item * L<URI|URI>, L<URI::Escape|URI::Escape>
585              
586             =item * L<WWW::NOS::Open::Article|WWW::NOS::Open::Article>
587              
588             =item * L<WWW::NOS::Open::AudioFragment|WWW::NOS::Open::AudioFragment>
589              
590             =item * L<WWW::NOS::Open::Broadcast|WWW::NOS::Open::Broadcast>
591              
592             =item * L<WWW::NOS::Open::DayGuide|WWW::NOS::Open::DayGuide>
593              
594             =item * L<WWW::NOS::Open::Document|WWW::NOS::Open::Document>
595              
596             =item * L<WWW::NOS::Open::Exceptions|WWW::NOS::Open::Exceptions>
597              
598             =item * L<WWW::NOS::Open::Result|WWW::NOS::Open::Result>
599              
600             =item * L<WWW::NOS::Open::TypeDef|WWW::NOS::Open::TypeDef>
601              
602             =item * L<WWW::NOS::Open::Version|WWW::NOS::Open::Version>
603              
604             =item * L<WWW::NOS::Open::Video|WWW::NOS::Open::Video>
605              
606             =item * L<XML::Simple|XML::Simple>
607              
608             =item * L<namespace::autoclean|namespace::autoclean>
609              
610             =back
611              
612             =head1 INCOMPATIBILITIES
613              
614             None.
615              
616             =head1 DIAGNOSTICS
617              
618             Exceptions in the form of an L<Exception::Class|Exception::Class> are thrown
619             when the Open NOS service reports an exception:
620              
621             =head2 C<NOSOpenUnauthorizedException>
622              
623             When a request was made without a valid API key, or from an IP address not
624             configured to be valid for that API key.
625              
626             =head2 C<NOSOpenExceededRangeException>
627              
628             When the time period for a guide request exceeds the supported range of 14
629             days.
630              
631             =head2 C<NOSOpenInternalServerErrorException>
632              
633             When an internal server error has occurred in the Open NOS service.
634              
635             =head2 C<NOSOpenBadRequestException>
636              
637             When the Open NOS service reports the request had bad syntax.
638              
639             =head1 BUGS AND LIMITATIONS
640              
641             Currently this module only uses the XML output of the Open NOS service and has
642             no option to use the JSON or serialized PHP formats. When the API matures the
643             other output options might be added and the content of the raw responses
644             exposed for further processing in an appropriate environment.
645              
646             Please report any bugs or feature requests at
647             L<RT for rt.cpan.org|https://rt.cpan.org/Dist/Display.html?Queue=WWW-NOS-Open>.
648              
649             =head1 AUTHOR
650              
651             Roland van Ipenburg, E<lt>ipenburg@xs4all.nlE<gt>
652              
653             =head1 LICENSE AND COPYRIGHT
654              
655             Copyright 2012 by Roland van Ipenburg
656              
657             This library is free software; you can redistribute it and/or modify
658             it under the same terms as Perl itself, either Perl version 5.14.0 or,
659             at your option, any later version of Perl 5 you may have available.
660              
661             =head1 DISCLAIMER OF WARRANTY
662              
663             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
664             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
665             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
666             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
667             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
668             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
669             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
670             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
671             NECESSARY SERVICING, REPAIR, OR CORRECTION.
672              
673             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
674             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
675             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENSE, BE
676             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
677             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
678             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
679             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
680             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
681             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
682             SUCH DAMAGES.
683              
684             =cut