Skip to content

Commit

Permalink
also detect format via extension
Browse files Browse the repository at this point in the history
  • Loading branch information
nichtich committed Jun 14, 2011
1 parent acd2eb3 commit 876148a
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 122 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -1 +1,4 @@
*~ *~
.build
*.tar.gz
RDF-Light-*
1 change: 1 addition & 0 deletions dist.ini
Expand Up @@ -27,3 +27,4 @@ bugtracker.web = https://github.com/nichtich/RDF-Light/issues
[PodSyntaxTests] [PodSyntaxTests]


[AutoPrereqs] [AutoPrereqs]
skip = ^TestPlackApp|RDF::Light::Node$
8 changes: 4 additions & 4 deletions example/app.psgi
Expand Up @@ -97,7 +97,7 @@ my $index_app = sub {
$tt->process("index.html", $vars, \$content); $tt->process("index.html", $vars, \$content);
utf8::downgrade($content); utf8::downgrade($content);


$env->{'psgix.logger'}->({ level => "info", message => "Index done:$content" }); #$env->{'psgix.logger'}->({ level => "info", message => "Index done:$content" });


return [ 200, ['Content-Type' => 'text/html'], [$content]]; return [ 200, ['Content-Type' => 'text/html'], [$content]];
}; };
Expand All @@ -106,9 +106,9 @@ my $index_app = sub {
builder { builder {
enable 'SimpleLogger'; enable 'SimpleLogger';
enable 'Debug'; enable 'Debug';
enable 'Plack::Middleware::Static', root => $dir, path => qr/\.css$/; enable 'JSONP'; # to support RDF/JSON in AJAX
enable 'JSONP'; # for RDF/JSON in AJAX enable 'Static', root => $dir, path => qr/\.css$/;
enable "+RDF::Light", source => $model, base => $base; enable '+RDF::Light', source => $model, base => $base;
enable $index_app; enable $index_app;
$app; $app;
}; };
Expand Down
182 changes: 109 additions & 73 deletions lib/RDF/Light.pm
@@ -1,7 +1,6 @@
package RDF::Light;

use strict; use strict;
use warnings; use warnings;
package RDF::Light;


=head1 NAME =head1 NAME
Expand All @@ -28,38 +27,6 @@ RDF::Light - Simplified Linked Data handling
$app; $app;
} }
=head1 INTRODUCTION
This package provides a PSGI application to serve RDF as Linked Data. In
contrast to other Linked Data applications, URIs must not have query parts and
the distinction between information-resources and non-information resources is
disregarded (some Semantic Web evangelists may be angry about this). By now
this package is experimental. For a more complete package see
L<RDF::LinkedData>.
The package implements a PSGI application that can be used as
L<Plack::Middleware> to provide RDF data. The implementation is based on
L<RDF::Trine> which is a full implementation of RDF standards in Perl.
=head1 OVERVIEW
An RDF::Light application processes PSGI/HTTP requests in three steps:
=over 4
=item 1
Determine query URI and serialization format (mime type) and set the request
variables C<rdflight.uri>, C<rdflight.type>, and C<rdflight.serializer>.
=item 2
Retrieve data about the resource which is identified by the request URI.
=item 3
Create a serialization.
=cut =cut


use Try::Tiny; use Try::Tiny;
Expand All @@ -71,7 +38,7 @@ use Carp;
use RDF::Light::Source; use RDF::Light::Source;


use parent 'Plack::Middleware'; use parent 'Plack::Middleware';
use Plack::Util::Accessor qw(source formats base); use Plack::Util::Accessor qw(source base formats via_param via_extension);


use parent 'Exporter'; use parent 'Exporter';
our @EXPORT_OK = qw(guess_serialization); our @EXPORT_OK = qw(guess_serialization);
Expand All @@ -91,14 +58,13 @@ our %rdf_formats = (
sub prepare_app { sub prepare_app {
my $self = shift; my $self = shift;


$self->formats( \%rdf_formats ) unless $self->formats;

# TODO: support array ref and custom serialization formats # TODO: support array ref and custom serialization formats
if ( $self->formats ) { ref $self->formats eq 'HASH'
ref $self->formats eq 'HASH' or carp 'formats must be a hash reference'; or carp 'formats must be a hash reference';
} else {
$self->formats( \%rdf_formats );
}


# TODO: support file extensions and disabling formats $self->via_param(1) unless defined $self->via_param;
} }


sub call { sub call {
Expand All @@ -114,7 +80,6 @@ sub call {
unless defined $env->{'rdflight.uri'}; unless defined $env->{'rdflight.uri'};


if ( $type ) { if ( $type ) {
# TODO: document this variables
$env->{'rdflight.type'} = $type; $env->{'rdflight.type'} = $type;
$env->{'rdflight.serializer'} = $serializer; $env->{'rdflight.serializer'} = $serializer;


Expand All @@ -124,9 +89,13 @@ sub call {
return [ 200, [ 'Content-Type' => $type ], [ $rdf_data ] ]; return [ 200, [ 'Content-Type' => $type ], [ $rdf_data ] ];
} }
} }


# pass through if no/unknown serializer or empty source (URI not found) or error # pass through if no/unknown serializer or empty source (URI not found) or error
return $app->( $env ); if ( $app ) {
return $app->( $env );
} else {
return [ 404, [ 'Content-Type' => 'text/plain' ], [ 'Not found' ] ];
}
} }


sub retrieve_and_serialize { sub retrieve_and_serialize {
Expand All @@ -146,20 +115,22 @@ sub retrieve_and_serialize {
if ( UNIVERSAL::isa( $src, 'CODE' ) ) { if ( UNIVERSAL::isa( $src, 'CODE' ) ) {
$rdf = $src->($env); $rdf = $src->($env);
} elsif ( UNIVERSAL::isa( $src, 'RDF::Trine::Model' ) ) { } elsif ( UNIVERSAL::isa( $src, 'RDF::Trine::Model' ) ) {
$rdf = $src->bounded_description( iri($env->{'rdflight.uri'}) ); $rdf = $src->bounded_description( iri($env->{'rdflight.uri'}) );
} elsif ( UNIVERSAL::can( $src, 'retrieve' ) ) {
$rdf = $src->retrieve( $env );
} }


if ( UNIVERSAL::isa( $rdf, 'RDF::Trine::Model' ) ) { if ( UNIVERSAL::isa( $rdf, 'RDF::Trine::Model' ) ) {
if ( $rdf->size > 0 ) { if ( $rdf->size > 0 ) {
return $serializer->serialize_model_to_string( $rdf ); return $serializer->serialize_model_to_string( $rdf );
} }
} elsif ( UNIVERSAL::isa( $rdf, 'RDF::Trine::Iterator' ) ) { } elsif ( UNIVERSAL::isa( $rdf, 'RDF::Trine::Iterator' ) ) {
if ( $rdf->peek ) { if ( $rdf->peek ) {
return $serializer->serialize_iterator_to_string( $rdf ); return $serializer->serialize_iterator_to_string( $rdf );
} }
} else { } else {
# TODO: how to indicate an error? (500?) # TODO: how to indicate an error? (500?)
# $env->{'rdflight.error'} = ... ? # $env->{'rdflight.error'} = ... ?
} }
} }


Expand All @@ -181,15 +152,26 @@ sub guess_serialization {


my $accept = $env->{HTTP_ACCEPT} || ''; my $accept = $env->{HTTP_ACCEPT} || '';
my $req = Plack::Request->new( $env ); my $req = Plack::Request->new( $env );
my $format = $req->param('format') || ''; # TODO: also support extensions my $format;

if ($self->via_param and $req->param('format')) {
$format = $req->param('format');
} elsif ($self->via_extension) {
my $path = $env->{PATH_INFO} || '';
if ( $path =~ /^(.*)\.([^.]+)$/ and $possible_formats->{$2} ) {
$env->{PATH_INFO} = $1;
$format = $2;
}
}


my ($type, $serializer); my ($type, $serializer);


if ($format ne '') { if ($format) {
try { my $name = $possible_formats->{$format};
$serializer = RDF::Trine::Serializer->new( $possible_formats->{$format} ); if ($name) { try {
$serializer = RDF::Trine::Serializer->new( $name );
($type) = $serializer->media_types; ($type) = $serializer->media_types;
} # TODO: catch if unknown format or format not available } } # TODO: catch if unknown format or format not available
} else { } else {
($type, $serializer) = try { ($type, $serializer) = try {
RDF::Trine::Serializer->negotiate( request_headers => $req->headers ); RDF::Trine::Serializer->negotiate( request_headers => $req->headers );
Expand All @@ -209,7 +191,7 @@ sub uri {


return $env->{'rdflight.uri'} if defined $env->{'rdflight.uri'}; return $env->{'rdflight.uri'} if defined $env->{'rdflight.uri'};


my ($base,$self); # TODO: support as second argument my ($base, $self); # TODO: support as second argument


if (UNIVERSAL::isa($env,'RDF::Light')) { if (UNIVERSAL::isa($env,'RDF::Light')) {
($self, $env) = ($env, shift); ($self, $env) = ($env, shift);
Expand All @@ -227,6 +209,40 @@ sub uri {
return $base.$path; return $base.$path;
} }


=head1 INTRODUCTION
This package provides a PSGI application to serve RDF as Linked Data. In
contrast to other Linked Data applications, URIs must not have query parts and
the distinction between information-resources and non-information resources is
disregarded (some Semantic Web evangelists may be angry about this). By now
this package is experimental. For a more complete package see
L<RDF::LinkedData>.
The package implements a PSGI application that can be used as
L<Plack::Middleware> to provide RDF data. The implementation is based on
L<RDF::Trine> which is a full implementation of RDF standards in Perl.
=head1 OVERVIEW
An RDF::Light application processes PSGI/HTTP requests in three steps:
=over 4
=item 1
Determine query URI and serialization format (mime type) and set the request
variables C<rdflight.uri>, C<rdflight.type>, and C<rdflight.serializer>.
=item 2
Retrieve data about the resource which is identified by the request URI.
=item 3
Create a serialization.
=back
=head1 METHODS =head1 METHODS
=head2 new ( [ %configuration ] ) =head2 new ( [ %configuration ] )
Expand All @@ -239,15 +255,27 @@ Creates a new object.
=item source =item source
Sets a code reference as RDF source (see L<RDF::Light::Source>) or a Sets a L<RDF::Trine::Model> or a code reference as RDF source that returns a
L<RDF::Trine::Model> to query from. You can also set an array reference Model or Iterator (see L<RDF::Light::Source>) to query from. You can also set
with a list of multiple sources, which are cascaded. an array reference with a list of multiple sources, which are cascaded.
For testing you can use the function dummy_source that always returns a single
triple and is exported by RDF::Light::Source.
=item base
Maps request URIs to a given URI prefix, similar to L<Plack::App::URLMap>.
For instance if you deploy you application at C<http://your.domain/> and set
base to C<http://other.domain/> then a request for C<http://your.domain/foo>
is be mapped to the URI C<http://other.domain/foo>.
=item formats =item formats
Defines supported serialization formats. You can either specify an array reference Defines supported serialization formats. You can either specify an array
with serializer names or a hash reference with mappings of format names to serializer reference with serializer names or a hash reference with mappings of format
names. Serializer names must exist in L<RDF::Trine::Serializer>::serializer_names. names to serializer names. Serializer names must exist in
RDF::Trine's L<RDF::Trine::Serializer>::serializer_names.
RDF::Light->new ( formats => [qw(ntriples rdfxml turtle)] ) RDF::Light->new ( formats => [qw(ntriples rdfxml turtle)] )
Expand All @@ -258,13 +286,22 @@ names. Serializer names must exist in L<RDF::Trine::Serializer>::serializer_name
ttl => 'turtle' ttl => 'turtle'
} ); } );
=item base By default the formats rdf, xml, and rdfxml (for L<RDF::Trine::Serializer>),
ttl (for L<RDF::Trine::Serializer::Turtle>), json
(for L<RDF::Trine::Serializer::RDFJSON>), and nt
(for L<RDF::Trine::Serializer::NTriples>) are used.
Maps request URIs to a given URI prefix, similar to L<Plack::App::URLMap>. =item via_param
For instance if you deploy you application at C<http://your.domain/> and set Detect serialization format via 'format' parameter. For instance
base to C<http://other.domain/> then a request for C<http://your.domain/foo> C<foobar?format=ttl> will serialize URI foobar in RDF/Turtle.
is be mapped to the URI C<http://other.domain/foo>. This is enabled by default.
=item via_extension
Detect serialization format via "file extension". For instance
C<foobar.rdf> will serialize URI foobar in RDF/XML.
This is disabled by default.
=item extensions =item extensions
Expand Down Expand Up @@ -296,8 +333,7 @@ and path. Query parameters are ignored.


=head2 SEE ALSO =head2 SEE ALSO
See also L<RDF::Light::Graph>. See also L<RDF::Light::Graph>, which is bundled with this module.
RDF::Light may be renamed to RDF::Light::LOD for "Linked Open Data".
=head2 ACKNOWLEDGEMENTS =head2 ACKNOWLEDGEMENTS
Expand Down
11 changes: 5 additions & 6 deletions lib/RDF/Light/Graph.pm
@@ -1,7 +1,6 @@
package RDF::Light::Graph;

use strict; use strict;
use warnings; use warnings;
package RDF::Light::Graph;


=head1 NAME =head1 NAME
Expand Down Expand Up @@ -35,7 +34,7 @@ sub new {
}, $class; }, $class;
} }


sub model { shift->{model} } sub model { $_[0]->{model} }


sub objects { sub objects {
my $self = shift; my $self = shift;
Expand All @@ -46,7 +45,7 @@ sub objects {
$subject = $self->node($subject) $subject = $self->node($subject)
unless UNIVERSAL::isa( $subject, 'RDF::Light::Node' ); unless UNIVERSAL::isa( $subject, 'RDF::Light::Node' );


my $all = 1 if ($property =~ s/^(.+[^_])_$/$1/); my $all = ($property =~ s/^(.+[^_])_$/$1/) ? 1 : 0;
my $predicate = $self->node($property); my $predicate = $self->node($property);


if (defined $predicate) { if (defined $predicate) {
Expand Down Expand Up @@ -287,11 +286,11 @@ sub new {
} }


sub uri { sub uri {
shift->trine->value shift->trine->uri_value
} }


sub href { # TODO: check whether non-XML characters are possible sub href { # TODO: check whether non-XML characters are possible
escapeHTML(shift->trine->value); escapeHTML(shift->trine->uri_value);
} }


sub objects { # TODO: rename to 'attr' or 'prop' ? sub objects { # TODO: rename to 'attr' or 'prop' ?
Expand Down

0 comments on commit 876148a

Please sign in to comment.