File Coverage

blib/lib/RDF/SKOS/Redland.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package RDF::SKOS::Redland;
2              
3 3     3   46923 use strict;
  3         6  
  3         119  
4 3     3   39 use warnings;
  3         5  
  3         88  
5 3     3   14 use Data::Dumper;
  3         7  
  3         188  
6              
7 3     3   1108 use RDF::SKOS::Scheme;
  3         8  
  3         80  
8 3     3   1058 use RDF::SKOS::Concept;
  3         7  
  3         75  
9              
10 3     3   17 use base 'RDF::SKOS';
  3         5  
  3         1176  
11            
12             our $SKOS_NS = "http://www.w3.org/2004/02/skos/core#";
13              
14 3     3   1655 use RDF::Redland::Query;
  0            
  0            
15              
16             =head1 NAME
17              
18             RDF::SKOS::Redland - SKOS - RDF::Redland based implementation
19              
20             =head1 SYNOPSIS
21              
22             my $model = ... # get an redland model
23              
24             use RDF::SKOS::Redland;
25             my $skos = new RDF::SKOS::Redland ($model);
26              
27             # API like RDF::SKOS
28              
29             =head1 DESCRIPTION
30              
31             One way to experience an SKOS object is to I it from an underlying RDF graph. As the SKOS
32             vocabulary is defined on the basis of RDF, this is well-defined.
33              
34             This package makes this translation, assuming an L model underneath. How you get that
35             model is your problem.
36              
37             At the moment, this is all read-only. That is, you cannot modify the SKOS object and expect that
38             this is reflected in the underlying RDF graph.
39              
40             =head2 Concept Identification
41              
42             Of course, being in the RDF world, concepts are identified via their URI (IRI, whatever).
43              
44             =head2 Annoyances
45              
46             =over
47              
48             =item
49              
50             You will get a STDERR warning:
51              
52             C
53              
54             That is harmless, but cause of the incomplete implementation of SPARQL on
55             top of Redland. Maybe I find a trick of working around, or maybe drop SPARQL
56             alltogether.
57              
58             =item
59              
60             With some versions of Redland you may get
61              
62             C<1/0Name "swig_runtime_data::type_pointer3 ....>
63              
64             Not sure what this is caused by.
65              
66             =back
67              
68             =head1 INTERFACE
69              
70             =head2 Constructor
71              
72             The constructor expects exactly one parameter: the RDF model from which the SKOS is derived. An
73             exception is raised if this is not so.
74              
75             =cut
76              
77             sub new {
78             my $class = shift;
79             my $model = shift;
80             die "no model" unless $model->isa ('RDF::Redland::Model');
81             my $self = bless { model => $model }, $class;
82             $self->{conceptClasses} = [ map { "<$_>" } _subclassesT ($model, $SKOS_NS.'Concept') ];
83             return $self;
84             }
85              
86             =pod
87              
88             =head2 Methods
89              
90             See L.
91              
92             =over
93              
94             =item B
95              
96             Given a concept IRI, this returns an L. This is read-only.
97              
98             =cut
99              
100             sub concept {
101             my $self = shift;
102             my $ID = shift;
103             return new RDF::SKOS::Concept ($self, $ID) if _is_instance ($self->{model}, $ID, @{ $self->{conceptClasses} });
104             }
105              
106             sub concepts {
107             my $self = shift;
108             return
109             map { new RDF::SKOS::Concept ($self, $_) }
110             _uniq (
111             map { _instances ($self->{model}, $_) }
112             @{ $self->{conceptClasses} }
113             );
114             }
115              
116             sub schemes {
117             my $self = shift;
118             return
119             map { new RDF::SKOS::Scheme ($self, $_) }
120             _instances ($self->{model}, 'skos:ConceptScheme' );
121             }
122              
123             sub scheme {
124             my $self = shift;
125             my $ID = shift;
126             return new RDF::SKOS::Scheme ($self, $ID) if _is_instance ($self->{model}, $ID, 'skos:ConceptScheme');
127             }
128              
129             sub topConcepts {
130             my $self = shift;
131             my $id = shift;
132             return
133             map { new RDF::SKOS::Concept ($self, $_) }
134             _navigate ($self->{model}, $id, 'skos:hasTopConcept');
135             }
136              
137             sub prefLabels {
138             my $self = shift;
139             my $id = shift;
140             return
141             _literal ($self->{model}, $id, 'skos:prefLabel');
142             }
143              
144             sub altLabels {
145             my $self = shift;
146             my $id = shift;
147             return
148             _literal ($self->{model}, $id, 'skos:altLabel');
149             }
150              
151             sub hiddenLabels {
152             my $self = shift;
153             my $id = shift;
154             return
155             _literal ($self->{model}, $id, 'skos:hiddenLabel');
156             }
157              
158             sub notes {
159             my $self = shift;
160             my $id = shift;
161             return
162             _literal ($self->{model}, $id, 'skos:note');
163             }
164              
165             sub scopeNotes {
166             my $self = shift;
167             my $id = shift;
168             return
169             _literal ($self->{model}, $id, 'skos:scopeNote');
170             }
171              
172             sub examples {
173             my $self = shift;
174             my $id = shift;
175             return
176             _literal ($self->{model}, $id, 'skos:example');
177             }
178              
179             sub historyNotes {
180             my $self = shift;
181             my $id = shift;
182             return
183             _literal ($self->{model}, $id, 'skos:historyNote');
184             }
185              
186             sub editorialNotes {
187             my $self = shift;
188             my $id = shift;
189             return
190             _literal ($self->{model}, $id, 'skos:editorialNote');
191             }
192              
193             sub changeNotes {
194             my $self = shift;
195             my $id = shift;
196             return
197             _literal ($self->{model}, $id, 'skos:changeNote');
198             }
199              
200              
201             sub narrower {
202             my $self = shift;
203             my $id = shift;
204             return
205             map { bless { id => $_, skos => $self }, 'RDF::SKOS::Concept' }
206             _uniq
207             ( _navigate ($self->{model}, $id, 'skos:narrower'),
208             _navigate ($self->{model}, $id, 'skos:broader', -1) )
209             ;
210             }
211              
212             sub narrowerTransitive {
213             my $self = shift;
214             my $id = shift;
215             return
216             map { bless { id => $_, skos => $self }, 'RDF::SKOS::Concept' }
217             _uniq
218             ( _narrowTrec ($self, $id, {}) );
219              
220             sub _narrowTrec {
221             my $self = shift;
222             my $id = shift;
223             my $seen = shift;
224             return () if $seen->{$id}++;
225             my @T = ( _navigate ($self->{model}, $id, 'skos:narrower'),
226             _navigate ($self->{model}, $id, 'skos:broader', -1) );
227             my @TT = map { _narrowTrec ($self, $_, $seen) }
228             @T;
229             return ($id, @T, @TT);
230             }
231             }
232              
233              
234             sub broader {
235             my $self = shift;
236             my $id = shift;
237             return
238             map { bless { id => $_, skos => $self }, 'RDF::SKOS::Concept' }
239             _uniq
240             ( _navigate ($self->{model}, $id, 'skos:narrower', -1),
241             _navigate ($self->{model}, $id, 'skos:broader') )
242             ;
243             }
244              
245             sub broaderTransitive {
246             my $self = shift;
247             my $id = shift;
248             return
249             map { bless { id => $_, skos => $self }, 'RDF::SKOS::Concept' }
250             _uniq
251             ( _broadTrec ($self, $id, {}) );
252             sub _broadTrec {
253             my $self = shift;
254             my $id = shift;
255             my $seen = shift;
256             return () if $seen->{$id}++;
257             my @T = ( _navigate ($self->{model}, $id, 'skos:narrower', -1),
258             _navigate ($self->{model}, $id, 'skos:broader') );
259             my @TT = map { _broadTrec ($self, $_, $seen) }
260             @T;
261             return ($id, @T, @TT);
262             }
263             }
264              
265             sub related {
266             my $self = shift;
267             my $id = shift;
268             return
269             map { bless { id => $_, skos => $self }, 'RDF::SKOS::Concept' }
270             _uniq
271             ( _navigate ($self->{model}, $id, 'skos:related', -1),
272             _navigate ($self->{model}, $id, 'skos:related') )
273             ;
274             }
275              
276             sub relatedTransitive {
277             my $self = shift;
278             my $id = shift;
279             return
280             map { bless { id => $_, skos => $self }, 'RDF::SKOS::Concept' }
281             _uniq
282             ( _relateTrec ($self, $id, {}) );
283             sub _relateTrec {
284             my $self = shift;
285             my $id = shift;
286             my $seen = shift;
287             return () if $seen->{$id}++;
288             my @T = ( _navigate ($self->{model}, $id, 'skos:related', -1),
289             _navigate ($self->{model}, $id, 'skos:related') );
290             my @TT = map { _relateTrec ($self, $_, $seen) }
291             grep { !$seen->{$_}++ }
292             @T;
293             return ($id, @T, @TT);
294             }
295             }
296              
297              
298             sub _navigate {
299             my $model = shift;
300             my $ID = shift;
301             my $PATH = shift;
302             my $inv = shift;
303              
304             my $q = new RDF::Redland::Query
305             ("SELECT ?a WHERE ".($inv ? "(?a $PATH <$ID>)" : "(<$ID> $PATH ?a)"). "
306             USING skos FOR <$SKOS_NS>");
307             my $res = $q->execute ($model);
308             my @ss;
309             while(!$res->finished) {
310             my %bs = $res->bindings;
311             # warn Dumper \%bs;
312             # warn $bs{a}->as_string;
313             push @ss, $bs{a}->as_string;
314             $res->next_result;
315             }
316             $res = undef;
317             return map { /\[(.*)\]/ ? $1 : $_ } @ss;
318             }
319              
320             sub _literal {
321             my $model = shift;
322             my $ID = shift;
323             my $PATH = shift;
324              
325             my $q = new RDF::Redland::Query
326             ("PREFIX skos: <$SKOS_NS>
327             SELECT ?l WHERE { <$ID> $PATH ?l }",
328             undef, undef, 'sparql');
329             my $res = $q->execute ($model);
330             my @ss;
331             while(!$res->finished) {
332             my %bs = $res->bindings;
333             # warn Dumper \%bs;
334             # warn $bs{l}->literal_value;
335             push @ss, [ $bs{l}->literal_value, $bs{l}->literal_value_language ];
336             $res->next_result;
337             }
338             $res = undef;
339             return @ss;
340             }
341              
342             sub _is_instance {
343             my $model = shift;
344             my $ID = shift;
345              
346             foreach my $CLASS (@_) {
347             my $q = new RDF::Redland::Query
348             ("PREFIX skos: <$SKOS_NS>
349             SELECT ?a WHERE
350             { <$ID> a $CLASS . }
351             ", undef, undef, 'sparql');
352             my $res = $q->execute ($model);
353             return 1 if $res->count > 0;
354             }
355             return 0;
356             }
357              
358             sub _instances {
359             my $model = shift;
360             my $TYPE = shift;
361              
362             my $q = new RDF::Redland::Query
363             ("PREFIX skos: <$SKOS_NS>
364             SELECT ?a WHERE
365             { ?a a $TYPE . }
366             ", undef, undef, 'sparql');
367             my $res = $q->execute ($model);
368             my @ss;
369             while(!$res->finished) {
370             my %bs = $res->bindings;
371             # warn Dumper \%bs;
372             push @ss, $bs{a}->as_string;
373             $res->next_result;
374             }
375             $res = undef;
376             return map { /\[(.*)\]/ && $1 } @ss;
377             }
378              
379             sub _subclassesT {
380             my $model = shift;
381             my $top = shift;
382              
383             my $q = new RDF::Redland::Query
384             ("PREFIX skos: <$SKOS_NS>
385             PREFIX rdfs:
386             SELECT ?c WHERE
387             { ?c rdfs:subClassOf <$top> . }
388             ", undef, undef, 'sparql');
389             my $res = $q->execute ($model);
390             my @ss;
391             while(!$res->finished) {
392             my %bs = $res->bindings;
393             # warn Dumper \%bs;
394             push @ss, $bs{c}->as_string;
395             $res->next_result;
396             }
397             $res = undef;
398             return _uniq ($top, map { _subclassesT ($model, $_) }
399             map { /\[(.*)\]/ && $1 }
400             @ss) ;
401             }
402              
403             sub _uniq {
404             my %X;
405             $X{$_}++ foreach @_;
406             return keys %X;
407             }
408              
409             =pod
410              
411             =back
412              
413             =head1 AUTHOR
414              
415             Robert Barta, C<< >>
416              
417             =head1 BUGS
418              
419             Please report any bugs or feature requests to C, or through
420             the web interface at L. I will be notified, and then you'll
421             automatically be notified of progress on your bug as I make changes.
422              
423             =head1 COPYRIGHT & LICENSE
424              
425             Copyright 2009 Robert Barta, all rights reserved.
426              
427             This program is free software; you can redistribute it and/or modify it under the same terms as Perl
428             itself.
429              
430             =cut
431              
432             our $VERSION = '0.01';
433              
434             "against all odds";
435              
436             __END__