File Coverage

lib/FAIR/Profile/Parser.pm
Criterion Covered Total %
statement 156 163 95.7
branch 27 56 48.2
condition n/a
subroutine 18 18 100.0
pod 1 2 50.0
total 202 239 84.5


line stmt bran cond sub pod time code
1             package FAIR::Profile::Parser;
2             $FAIR::Profile::Parser::VERSION = '0.230';
3              
4             # ABSTRACT: Parser that reads FAIR Profile RDF and creates a FAIR::Profile object
5              
6 2     2   2809 use Moose;
  2         4  
  2         17  
7              
8 2     2   12468 use strict;
  2         4  
  2         46  
9 2     2   10 use Carp;
  2         53  
  2         152  
10 2     2   12 use RDF::Trine::Parser;
  2         3  
  2         57  
11 2     2   8 use RDF::Trine::Model;
  2         3  
  2         64  
12 2     2   890 use RDF::Query;
  2         428092  
  2         78  
13 2     2   766 use LWP::Simple;
  2         8446  
  2         19  
14 2     2   735 use FAIR::NAMESPACES;
  2         3  
  2         138  
15 2     2   7 use FAIR::Profile;
  2         3  
  2         2586  
16              
17             #use vars qw /$VERSION/;
18             #$VERSION = sprintf "%d.%02d", q$Revision: 1.5 $ =~ /: (\d+)\.(\d+)/;
19              
20              
21              
22              
23              
24              
25              
26              
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37             has filename => (
38             is => 'rw',
39             isa => "Str",
40             required => 0,
41             );
42              
43             has data => (
44             is => 'rw',
45             isa => "Str",
46             required => 0,
47             );
48              
49             has data_format => (
50             is => 'rw',
51             isa => "Str",
52             required => 0,
53             );
54              
55             has model => (
56             is => 'rw',
57             isa => 'RDF::Trine::Model',
58             default => sub { my $store = RDF::Trine::Store::Memory->new();
59             my $model = RDF::Trine::Model->new($store); return $model;},
60             );
61              
62             has profile => (
63             is => 'rw',
64             isa => 'FAIR::Profile',
65            
66             );
67              
68             sub parse {
69            
70 1     1 1 1180 my ($self) = @_;
71 1         26 my $filename = $self->filename;
72 1         18 my $model = $self->model;
73 1         17 my $data = $self->data;
74 1 50       6 if ($filename) {
    50          
75 0 0       0 if ($filename =~ m'^http://') {
76 0         0 my $result = get($filename);
77 0 0       0 die "Nothing could be retrieved from $filename\n" unless $result;
78 0         0 RDF::Trine::Parser->parse_url_into_model($filename, $model );
79            
80             } else {
81            
82 0 0       0 die "file $filename does not exist" unless (-e $filename);
83             # open(IN, "$filename") || die "can't open input file $!\n";
84 0         0 RDF::Trine::Parser->parse_file_into_model( "", $filename, $model );
85            
86             }
87             } elsif ($data){
88 1 50       24 die "you can't pass data without telling me the ->data_format" unless ($self->data_format);
89 1         21 my $parser = RDF::Trine::Parser->new($self->data_format);
90 1         4915787 $parser->parse_into_model("", $data, $model );
91             }
92            
93            
94             # $self->model($model); # this appears to be redundant...
95            
96 1         75161 my $profile = $self->getProfile();
97 1         96 return $profile;
98             }
99              
100             sub getProfile {
101 1     1 0 5 my ($self) = @_;
102 1         67 my $model = $self->model;
103              
104 1         7 my $query = RDF::Query->new( "SELECT ?s WHERE {?s a <".FAIR."FAIRProfile>}" );
105 1         7470 my $iterator = $query->execute( $model );
106 1         6973 my $profile;
107 1         7 while (my $row = $iterator->next) {
108 1         1170 my $URI = $row->{ 's' };
109 1         8 $profile = $self->_fillProfile($URI);
110             }
111 1         844 $self->profile($profile);
112 1         4 return $profile;
113              
114             }
115              
116              
117              
118             sub _fillProfile {
119 1     1   2 my ($self, $URITrine) = @_;
120             #has URI => (
121             #has type => (
122             #has hasClass => (
123             #has label => (
124             #has title => (
125             #has description => (
126             #has license => (
127             #has organization => (
128             #has identifier => (
129             #has schemardfs_URL => (
130              
131 1         71 my %ns = %FAIR::Base::predicate_namespaces;
132 1         8 my $prefixes = _generatePrefixHeader();
133 1         4 my $query = "SELECT ?label ?title ?description ?modified ?license ?issued ?organization ?identifier ?schemardfs_URL ?type
134             WHERE { ";
135 1         2 my $whereclause = "";
136 1         13 my $URI = $URITrine->value;
137 1         12 foreach my $element(qw(label title description modified license issued organization identifier schemardfs_URL type)){
138 10         55 $whereclause .= "OPTIONAL {<$URI> $ns{$element}:$element ?$element} .\n";
139             }
140 1         10 $query = $prefixes . $query . $whereclause . "}";
141             #print STDERR $query;
142 1         10 my $Q = RDF::Query->new( $query );
143 1         40040 my $iterator = $Q->execute( $self->model );
144 1         39557 my $row = $iterator->next; # should only be one!
145            
146 1 50       13171 my $label = $row->{label}->value if $row->{label};
147 1 50       45 my $title = $row->{title}->value if $row->{title};
148 1 50       33 my $description = $row->{description}->value if $row->{description};
149 1 50       34 my $modified = $row->{modified}->value if $row->{modified};
150 1 50       32 my $license = $row->{license}->value if $row->{license};
151 1 50       34 my $issued = $row->{issued}->value if $row->{issued};
152 1 50       32 my $organization = $row->{organization}->value if $row->{organization};
153 1 50       32 my $identifier = $row->{identifier}->value if $row->{identifier};
154 1 50       33 my $schemardfs_URL = $row->{schemardfs_URL}->value if $row->{schemardfs_URL};
155 1 50       4 my $type = $row->{type}->value if $row->{type};
156 1         31 my $ProfileObject = FAIR::Profile->new(
157             URI => $URI,
158             label => $label,
159             title => $title,
160             description => $description,
161             modified => $modified,
162             license => $license,
163             issued => $issued,
164             organization => $organization,
165             identifier => $identifier,
166             # schemardfs_URL => $schemardfs_URL,
167             );
168            
169 1         1018 $self->profile($ProfileObject);
170            
171 1         4 $self->_fillClasses();
172            
173 1         359 return $self->profile();
174            
175            
176             }
177              
178             sub _fillClasses {
179 1     1   2 my ($self) = @_;
180 1         19 my $ProfileObject = $self->profile;
181 1         21 my $model = $self->model;
182 1         53 my %ns = %FAIR::Base::predicate_namespaces;
183            
184 1         22 my $ProfileURI = $ProfileObject->URI;
185              
186 1         6 my $prefixes = _generatePrefixHeader();
187            
188 1         14 my $query = RDF::Query->new( "$prefixes
189             SELECT ?c WHERE {?c $ns{provenance}:provenance <$ProfileURI>}" );
190 1         7376 my $iterator = $query->execute( $model );
191 1         3551 my $profile;
192 1         6 while (my $row = $iterator->next) {
193 1 50       512 next unless $row->{'c'};
194 1         18 my $ClassURI = $row->{ 'c' }->value;
195 1         11 my $class = $self->_fillClass($ClassURI);
196 1         381 $ProfileObject->add_Class($class);
197            
198 1         4 $self->_fillProperties($class);
199             }
200            
201            
202             }
203              
204             sub _fillClass {
205              
206 1     1   3 my ($self, $ClassURI) = @_;
207              
208             #label => ['Descriptor Profile Schema Class', 'read'],
209             #class_type => [undef, 'read/write'], # this is a URI to an OWL class or RDFS class
210              
211 1         41 my $model = $self->model;
212 1         45 my %ns = %FAIR::Base::predicate_namespaces;
213            
214 1         6 my $prefixes = _generatePrefixHeader();
215 1         2 my $query = "SELECT ?label ?onClassType
216             WHERE { ";
217 1         3 my $whereclause = "";
218 1         2 foreach my $element(qw(label onClassType)){
219 2         13 $whereclause .= "OPTIONAL {<$ClassURI> $ns{$element}:$element ?$element} .\n";
220             }
221             #print STDERR $whereclause;
222 1         5 $query = $prefixes . $query . $whereclause . "}";
223 1         6 my $Q = RDF::Query->new( $query );
224 1         12222 my $iterator = $Q->execute( $self->model );
225 1         10045 my $row = $iterator->next; # should only be one!
226            
227 1 50       2937 my $label = $row->{label}->value if $row->{label};
228 1 50       44 my $class_type = $row->{onClassType}->value if $row->{onClassType};
229 1         32 my $ClassObject = FAIR::Profile::Class->new(
230             URI => $ClassURI,
231             label => $label,
232             onClassType => $class_type,
233             );
234            
235 1         488 return $ClassObject;
236              
237            
238             }
239              
240             sub _fillProperties {
241 1     1   3 my ($self, $ClassObject) = @_;
242 1         21 my $model = $self->model;
243 1         31 my %ns = %FAIR::Base::predicate_namespaces;
244              
245 1         23 my $ClassURI = $ClassObject->URI;
246            
247 1         4 my $prefixes = _generatePrefixHeader();
248 1         11 my $query = RDF::Query->new( "$prefixes
249             SELECT ?p WHERE {<$ClassURI> $ns{hasProperty}:hasProperty ?p}" );
250 1         7323 my $iterator = $query->execute( $model );
251 1         3806 my $profile;
252 1         8 while (my $row = $iterator->next) {
253 2 50       965 next unless $row->{'p'};
254 2         33 my $PropertyURI = $row->{ 'p' }->value;
255 2         17 my $property = $self->_fillProperty($PropertyURI);
256 2         963 $ClassObject->add_Property($property);
257             }
258              
259             }
260              
261             sub _fillProperty {
262            
263 2     2   3 my ($self, $PropertyURI) = @_;
264              
265             #property_type => [ undef, 'read/write' ], # a URI referring to an ontological predicate
266             #_allowed_values => [undef, 'read/write' ], # this is a list of URL references to either other Profiles, or to SKOS view on an ontology (Jupp et al, 2013)
267             #label => ['Descriptor Profile Schema Property', 'read'],
268             #allow_multiple => ['true', 'read/write'], # can this property appear multiple times?
269              
270              
271 2         82 my $model = $self->model;
272            
273 2         78 my %ns = %FAIR::Base::predicate_namespaces;
274 2         11 my $prefixes = _generatePrefixHeader();
275 2         5 my $query = "SELECT ?label ?onPropertyType ?maxCount ?minCount
276             WHERE { ";
277 2         4 my $whereclause = "";
278 2         4 foreach my $element(qw(label onPropertyType maxCount minCount)){
279 8         32 $whereclause .= "OPTIONAL {<$PropertyURI> $ns{$element}:$element ?$element} .\n";
280             }
281             #print STDERR $whereclause;
282 2         7 $query = $prefixes . $query . $whereclause . "}";
283 2         12 my $Q = RDF::Query->new( $query );
284 2         36205 my $iterator = $Q->execute( $self->model );
285 2         25597 my $row = $iterator->next; # should only be one!
286            
287 2 50       10553 my $label = $row->{label}->value if $row->{label};
288 2 50       78 my $property_type = $row->{onPropertyType}->value if $row->{onPropertyType};
289 2 50       41 my $maxCount = $row->{maxCount}->value if $row->{maxCount};
290 2 100       66 my $minCount = $row->{minCount}->value if $row->{minCount};
291 2         52 my $PropertyObject = FAIR::Profile::Property->new(
292             URI => $PropertyURI,
293             label => $label,
294             onPropertyType => $property_type,
295             );
296 2 50       1783 if (defined $maxCount) {
297 2         44 $PropertyObject->maxCount($maxCount);
298             }
299 2 100       6 if (defined $minCount) {
300 1         20 $PropertyObject->minCount($minCount);
301             }
302              
303 2         5 my $query2 = "SELECT ?allowedValues
304             WHERE { ";
305 2         3 my $whereclause2 = "";
306 2         6 foreach my $element(qw(allowedValues)){
307 2         16 $whereclause2 .= "OPTIONAL {<$PropertyURI> $ns{$element}:$element ?$element} .\n";
308             }
309             #print STDERR $whereclause2;
310 2         8 $query2 = $prefixes . $query2 . $whereclause2 . "}";
311 2         18 my $Q2 = RDF::Query->new( $query2 );
312 2         16592 my $iterator2 = $Q2->execute( $self->model );
313 2         8125 while (my $row = $iterator2->next){
314 2 50       1673 my $restrictionURI = $row->{allowedValues}->value if $row->{allowedValues};
315 2 50       12 next unless $restrictionURI;
316 0         0 $PropertyObject->add_AllowedValue($restrictionURI);
317             }
318            
319            
320 2         196 return $PropertyObject;
321              
322            
323             }
324              
325             sub _generatePrefixHeader {
326 2     2   10 no strict "refs";
  2         4  
  2         181  
327 6     6   13 my $header = "";
328 6         24 foreach my $namespace (qw(DCAT
329             DC
330             DCTYPE
331             FOAF
332             RDF
333             RDFS
334             SKOS
335             VCARD
336             XSD
337             FAIR)) {
338 60         205 $header = $header . "PREFIX $namespace: <".&$namespace.">\n";
339             }
340 6         16 return $header
341             }
342              
343             1;
344              
345             __END__
346              
347             =pod
348              
349             =encoding UTF-8
350              
351             =head1 NAME
352              
353             FAIR::Profile::Parser - Parser that reads FAIR Profile RDF and creates a FAIR::Profile object
354              
355             =head1 VERSION
356              
357             version 0.230
358              
359             =head1 SYNOPSIS
360              
361             use FAIR::Profile::Parser;
362              
363             my $parser = FAIR::Profile::Parser->new(filename => "./ProfileSchema.rdf");
364             my $DatasetSchema = $parser->parse;
365              
366             my $schema = $DatasetSchema->serialize;
367             open(OUT, ">ProfileSchema2.rdf")
368             print OUT $schema;
369             close OUT;
370              
371             =head1 DESCRIPTION
372              
373             FAIR Profiles describe the metadata elements, and constrained values, that should be
374             associated with a given information entity. They ARE NOT containers for this metadata,
375             they only describe what that metadata should look like (meta-meta-data :-) )
376              
377             This module will parse an RDF file containing a FAIR Profile into
378             objects that can be used to construct a metadata capture interface.
379             The objects will tell you what fields are required/optional, and what possible
380             values they are allowed to contain.
381              
382             =head1 NAME
383              
384             FAIR::Profile::Parser - a module for reading FAIR Profile RDF files
385              
386             =head1 AUTHORS
387              
388             Mark Wilkinson (markw at illuminae dot com)
389              
390             =head1 METHODS
391              
392             =head2 new
393              
394             Title : new
395             Usage : my $ProfileParser = FAIR::Profile::Parser->new();
396             Function: Builds a new FAIR::Profile::Parser
397             Returns : FAIR::Profile::Parser
398             Args : filename => $filename
399             model => $model (an existing RDF::Trine::Model -
400             if you don't supply this it will be created for you)
401              
402             =head2 parse
403              
404             Title : parse
405             Usage : my $ProfileObject = $ProfileParser->parse();
406             Function: parse the file associated with the Parser
407             Returns : FAIR::Profile
408             Args : none
409              
410             =head2 filename
411              
412             Title : filename
413             Usage : $ProfileParser->filename($filename);
414             Function: associate a file with the parser
415             Returns : null
416             Args : full or relative path to the file to be parsed
417              
418             =head2 data
419              
420             Title : data
421             Usage : $ProfileParser->data($rdfdata);
422             Function: associate a data string with the parser
423             Returns : null
424             Args : string of RDF in the format specified in $Parser->data_format
425              
426             =head2 data_format
427              
428             Title : data_format
429             Usage : $ProfileParser->data_format($format);
430             Function: the format of the RDF data in ->data (if any)
431             Returns : null
432             Args : rdfxml | turtle | ntriples | nquads (or any type acceptable to RDF::Trine)
433              
434             =head2 model
435              
436             Title : model
437             Usage : $ProfileParser->model($RDFTrineModel);
438             Function: associate an RDF::Trine::Model with the parser
439             Returns : null
440             Args : RDF::Trine::Model (this will be created for you, if not supplied)
441              
442             =head2 profile
443              
444             Title : profile
445             Usage : $Profile = $ProfileParser->profile;
446             Function: retrieve the profile after a parse. Must parse first!
447             Returns : FAIR::Profile
448             Args : none
449              
450             =head1 AUTHOR
451              
452             Mark Denis Wilkinson (markw [at] illuminae [dot] com)
453              
454             =head1 COPYRIGHT AND LICENSE
455              
456             This software is Copyright (c) 2015 by Mark Denis Wilkinson.
457              
458             This is free software, licensed under:
459              
460             The Apache License, Version 2.0, January 2004
461              
462             =cut