Skip to content

Commit

Permalink
added filters
Browse files Browse the repository at this point in the history
  • Loading branch information
nichtich committed Jun 7, 2011
1 parent a73cb54 commit 47dffb8
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 39 deletions.
14 changes: 7 additions & 7 deletions example/country.html
@@ -1,18 +1,18 @@
<html>
[% c = uri %]
<head>
<!-- TODO: first get english name -->
<title>[% uri.name %]</title>
<title>[% c.name('@en') %]</title>
</head>
<body>
<h1>[% uri.name %]</h1>
<h1>[% c.name('@en') %]</h1>
<dl>
[% IF uri.alpha__2 %]
[% IF c.alpha__2 %]
<dd>ISO 3166 alpha-2</dd>
<dd>[% uri.alpha__2 %]</dd>
<dd>[% c.alpha__2 %]</dd>
[% END %]
[% IF uri.alpha__3 %]
[% IF c.alpha__3 %]
<dd>ISO 3166 alpha-3</dd>
<dd>[% uri.alpha__3 %]</dd>
<dd>[% c.alpha__3 %]</dd>
[% END %]
</dl>
[% INCLUDE "footer.html" %]
Expand Down
154 changes: 125 additions & 29 deletions lib/RDF/Light/Graph.pm
Expand Up @@ -9,12 +9,13 @@ RDF::Light::Graph - Lightweight access to RDF data
=head1 DESCRIPTION
This package provides some classes that wrap L<RDF::Trine::Node> and its
subclasses for easy use of RDF data, especially within L<Template> Toolkit.
Basically there is RDF::Light::Graph for RDF graphs and there are
RDF::Light::Literal, RDF::Light::Resource, and RDF::Light::Blank for RDF nodes,
which each belong to an RDF graph. Internally each node is represented by a
L<RDF::Trine::Node> objects that is connected to a particular RDF::Light::Graph.
This package provides classes for a node-centric API to access RDF data. The
classes wrap L<RDF::Trine::Node> and its subclasses for easy use of RDF data,
especially within L<Template> Toolkit. Basically there is RDF::Light::Graph
for RDF graphs and there are RDF::Light::Literal, RDF::Light::Resource, and
RDF::Light::Blank for RDF nodes, which each belong to an RDF graph. Internally
each node is represented by a L<RDF::Trine::Node> objects that is connected to
a particular RDF::Light::Graph.
=cut

Expand All @@ -25,8 +26,8 @@ our $AUTOLOAD;

sub new {
my ($class, %arg) = @_;
my $namespaces = $arg{namespaces} || RDF::Trine::NamespaceMap->new;
my $model = $arg{model} || RDF::Trine::Model->new;
my $namespaces = $arg{namespaces} || RDF::Trine::NamespaceMap->new;
my $model = $arg{model} || RDF::Trine::Model->new;

bless {
namespaces => $namespaces,
Expand All @@ -37,7 +38,10 @@ sub new {
sub model { shift->{model} }

sub objects {
my ($self, $subject, $property) = @_;
my $self = shift;
my $subject = shift;
my $property = shift;
my @filter = @_;

$subject = $self->node($subject)
unless UNIVERSAL::isa( $subject, 'RDF::Light::Node' );
Expand All @@ -47,12 +51,19 @@ sub objects {

if (defined $predicate) {
my @objects = $self->{model}->objects( $subject->trine, $predicate->trine );

@objects = map { $self->node( $_ ) } @objects;

# TODO apply filters one by one and return in order of filters
@objects = grep { $_->is(@filter) } @objects
if @filter;

return unless @objects;

if ($all) {
return [ map { $self->node( $_ ) } @objects ];
return \@objects;
} else {
return $self->node( $objects[0] );
return $objects[0];
}
}

Expand Down Expand Up @@ -106,7 +117,7 @@ sub add {
if (UNIVERSAL::isa($add, 'RDF::Trine::Statement')) {
$self->model->add_statement( $add );
} elsif (UNIVERSAL::isa($add, 'RDF::Trine::Iterator')) {
# No RDF::Trine::Model::add_iterator ?
# Is there no RDF::Trine::Model::add_iterator ??
while (my $st = $add->next) {
$self->add( $st );
}
Expand All @@ -128,16 +139,57 @@ sub AUTOLOAD {

package RDF::Light::Node;

our $AUTOLOAD;

sub trine { shift->[0]; }
sub graph { shift->[1]; }
sub esc { shift->str; }

sub is_literal { shift->[0]->is_literal; }
sub is_resource { shift->[0]->is_resource; }
sub is_blank { shift->[0]->is_blank; }

sub _autoload { }

sub AUTOLOAD {
my $self = shift;
return if !ref($self) or $AUTOLOAD =~ /^(.+::)?DESTROY$/;

my $method = $AUTOLOAD;
$method =~ s/.*:://;

return $self->_autoload( $method, @_ );
}

sub is {
my $self = shift;
return 1 unless @_;

foreach my $check (@_) {
if ($self->is_literal) {
return 1 if $check eq '' or $check eq 'literal';
return 1 if $check eq '@' and $self->lang;
return 1 if $check =~ /^@(.+)/ and $self->lang($1);
return 1 if $check eq /^\^\^?$/ and $self->datatype;
} elsif ($self->is_resource) {
return 1 if $check eq ':' or $check eq 'resource';
} elsif ($self->is_blank) {
return 1 if $check eq '-' or $check eq 'blank';
}
}

return 0;
}

package RDF::Light::Node::Literal;
use base 'RDF::Light::Node';
use CGI qw(escapeHTML);

use overload '""' => sub { shift->str; };

# not very strict check for language tag look-alikes (see www.langtag.net)
our $LANGTAG = qr/^(([a-z]{2,8}|[a-z]{2,3}-[a-z]{3})(-[a-z0-9_]+)?-?)$/;

sub new {
my $class = shift;
my $graph = shift || RDF::Light::Node::Graph->new;
Expand All @@ -155,7 +207,21 @@ sub str { shift->trine->literal_value }

sub esc { escapeHTML( shift->trine->literal_value ) }

sub lang { shift->trine->literal_value_language } # TODO: 'language' object?
sub lang {
my $self = shift;
my $lang = $self->trine->literal_value_language;
return $lang if not @_ or not $lang;

my $xxx = shift || "";
$xxx =~ s/_/-/g;
return unless $xxx =~ $LANGTAG;

if ( $xxx eq $lang or $xxx =~ s/-$// and index($lang, $xxx) == 0 ) {
return $lang;
}

return;
}

sub type {
my $self = shift;
Expand All @@ -164,6 +230,17 @@ sub type {

# we may use a HTML method for xml:lang="lang">$str</

sub _autoload {
my $self = shift;
my $method = shift;

return unless $method =~ /^is_(.+)$/;

# We assume that no language is named 'blank', 'literal', or 'resource'
return 1 if $self->lang($1);

return;
}

package RDF::Light::Node::Blank;
use base 'RDF::Light::Node';
Expand Down Expand Up @@ -194,8 +271,6 @@ use CGI qw(escapeHTML);

use overload '""' => sub { shift->str; };

our $AUTOLOAD;

sub new {
my $class = shift;
my $graph = shift || RDF::Light::Node::Graph->new;
Expand Down Expand Up @@ -226,16 +301,11 @@ sub objects { # TODO: rename to 'attr' or 'prop' ?
*esc = *href;
*str = *uri;

sub AUTOLOAD {
my $self = shift;
return if !ref($self) or $AUTOLOAD =~ /^(.+::)?DESTROY$/;

my $property = $AUTOLOAD;
$property =~ s/.*:://;

sub _autoload {
my $self = shift;
my $property = shift;
return if $property =~ /^(query|lang)$/; # reserved words

return $self->objects( $property );
return $self->objects( $property, @_ );
}

1;
Expand All @@ -261,7 +331,7 @@ To convert a RDF::Trine::Node object into a RDF::Light::Node, you can use:
Note that all these methods silently return undef on failure.
Each RDF::Light::Node provides at least three access methods:
Each RDF::Light::Node provides at least the following methods:
=over 4
Expand All @@ -275,6 +345,14 @@ called on string conversion (C<< "$x" >> equals C<< $x->str >>).
Returns a HTML-escaped string representation. This can safely be used
in HTML and XML.
=item is_literal / is_resource / is_blank
Returns true if the node is a literal / resource / blank node.
=item is ( $check1 [, $check2 ... ] )
Checks whether the node fullfills some matching criteria.
=item trine
Returns the underlying L<RDF::Trine::Node>.
Expand All @@ -295,12 +373,20 @@ In addition for literal nodes:
=item lang
...
Return the literal's language tag (if the literal has one).
=item type
...
=item is_xxx
Returns whether the literal has language tag xxx, where xxx is a BCP 47 language
tag locator. For instance C<is_en> matches language tag C<en> (but not C<en-us>),
C<is_en_us> matches language tag C<en-us> and C<is_en_> matches C<en> and all
language tags that start with C<en->. Use C<lang> to check whether there is any
language tag.
=back
In addition for blank nodes:
Expand Down Expand Up @@ -330,12 +416,22 @@ In addition for resource nodes:
Any other method name is used to query objects. The following three statements
are equivalent:
$x->foaf_name;
$x->objects('foaf_name');
$x->graph->objects( $x, 'foaf_name' );
$x->foaf_name;
$x->objects('foaf_name');
$x->graph->objects( $x, 'foaf_name' );
=back
You can also add filters in a XPath-like language (the use of RDF::Light::Graph
in a template is an example of a "RDFPath" language):
$x->dc_title('@en') # literal with language tag @en
$x->dc_title('@en-') # literal with language tag @en or @en-...
$x->dc_title('') # any literal
$x->dc_title('@') # literal with any language tag
$x->dc_title('^') # literal with any datatype
$x->foaf_knows(':') # any resource
...
=head1 GRAPH METHODS
Expand Down
34 changes: 31 additions & 3 deletions t/40_graph.t
Expand Up @@ -2,7 +2,7 @@ use strict;
use warnings;

use Test::More;
use RDF::Trine qw(iri literal blank);
use RDF::Trine qw(iri literal blank statement);
use RDF::Trine::NamespaceMap;
use RDF::Trine::Parser;
use Data::Dumper;
Expand All @@ -14,27 +14,42 @@ isa_ok $graph, 'RDF::Light::Graph';

my $lit = $graph->node( literal("Geek & Poke") );
isa_ok $lit, 'RDF::Light::Node::Literal';
ok ($lit->is_literal && !$lit->is_resource && !$lit->is_blank, 'is_literal');
is $lit->str, 'Geek & Poke', 'stringify literal';
is $lit->esc, 'Geek &amp; Poke', 'HTML escape literal';
is $lit->type, undef, 'untyped literal';

is $graph->literal("Geek & Poke")->str, $lit->str, 'construct via ->literal';

diag('language tags');
my $l1 = $graph->literal("bill","en-GB");
my $l2 = $graph->literal("check","en-US");
is "$l1", "bill", 'literal with language code';
is $l1->lang, 'en-gb';
is $l2->lang, 'en-us';
ok $l1->is_en_gb && !$l2->is_en_gb, 'is_en_gb';
ok !$l1->is_en_us && $l2->is_en_us, 'is_en_us';
ok $l1->is_en_ && $l2->is_en_ && !$l1->is_en, 'is_en_';
ok $l1->is('@') && $l1->is('@en-'), 'is(...)';

$l1 = $graph->literal("love","en");
ok $l1->is_en && $l1->is_en_, 'is_en_ and is_en';


diag('blank nodes');
my $blank = $graph->node( blank('x1') );
isa_ok $blank, 'RDF::Light::Node::Blank';
ok (!$blank->is_literal && !$blank->is_resource && $blank->is_blank, 'is_blank');
is $blank->id, 'x1', 'blank id';

is $graph->blank("x1")->id, $blank->id, 'construct via ->blank';
is RDF::Light::Node::Blank->new( $graph, 'x1' )->id, $blank->id, 'blank constructor';


diag('resource nodes');
my $uri = $graph->node( iri('http://example.com/"') );
isa_ok $uri, 'RDF::Light::Node::Resource';
ok (!$uri->is_literal && $uri->is_resource && !$uri->is_blank, 'is_resource');
is "$uri", 'http://example.com/"', 'stringify URI';
is $uri->href, 'http://example.com/&quot;', 'HTML escape URI';
is $uri->esc, 'http://example.com/&quot;', 'HTML escape URI';
Expand Down Expand Up @@ -62,14 +77,27 @@ $obj = $a->foaf_name;
is_deeply( "$obj", 'Alice', 'literal object');

$obj = $a->zonk;
is_deeply( "$obj", 'Foo', 'property with default namespace');
is_deeply( "$obj", 'foo', 'property with default namespace');

is $graph->node('alice')->uri, 'http://example.org/alice';
is $graph->bob->foaf_name->str, 'Bob', 'chaining accesors';

is $graph->foaf_name->uri, 'http://xmlns.com/foaf/0.1/name', 'namespace URI';
is $graph->foaf__name->uri, 'http://example.org/foaf_name', 'non-namespace URI';

$graph->add( statement(
iri('http://example.org/alice'),
iri('http://example.org/zonk'),
literal('bar','fr'),
));

$obj = $a->zonk('@fr');
is_deeply( "$obj", 'bar', 'property with filter');

# TODO:
# $obj = $a->zonk('@fr','');
# is_deeply( "$obj", 'bar', 'property with filter');

done_testing;

__DATA__
Expand All @@ -79,5 +107,5 @@ __DATA__
<http://example.org/bob> foaf:knows <http://example.org/alice> .
<http://example.org/alice> foaf:name "Alice" .
<http://example.org/bob> foaf:name "Bob" .
<http://example.org/alice> x:zonk "Foo" .
<http://example.org/alice> x:zonk "foo"@en .

0 comments on commit 47dffb8

Please sign in to comment.