File Coverage

blib/lib/RDF/aREF/Decoder.pm
Criterion Covered Total %
statement 191 222 86.0
branch 104 142 73.2
condition 30 54 55.5
subroutine 34 39 87.1
pod 5 18 27.7
total 364 475 76.6


line stmt bran cond sub pod time code
1             package RDF::aREF::Decoder;
2 9     9   31688 use strict;
  9         20  
  9         348  
3 9     9   43 use warnings;
  9         14  
  9         227  
4 9     9   90 use v5.10;
  9         88  
  9         553  
5              
6             our $VERSION = '0.25';
7              
8 9     9   4828 use RDF::NS;
  9         90808  
  9         1028  
9 9     9   79 use Carp qw(croak carp);
  9         20  
  9         617  
10 9     9   46 use Scalar::Util qw(refaddr reftype blessed);
  9         11  
  9         458  
11              
12 9     9   4933 use parent 'Exporter';
  9         3145  
  9         47  
13             our @EXPORT_OK = qw(prefix localName qName blankNode blankNodeIdentifier
14             IRIlike languageString languageTag datatypeString);
15              
16             our ($PREFIX, $NAME);
17             BEGIN {
18 9     9   1080 my $nameChar = 'A-Z_a-z\N{U+00C0}-\N{U+00D6}\N{U+00D8}-\N{U+00F6}\N{U+00F8}-\N{U+02FF}\N{U+0370}-\N{U+037D}\N{U+037F}-\N{U+1FFF}\N{U+200C}-\N{U+200D}\N{U+2070}-\N{U+218F}\N{U+2C00}-\N{U+2FEF}\N{U+3001}-\N{U+D7FF}\N{U+F900}-\N{U+FDCF}\N{U+FDF0}-\N{U+FFFD}\N{U+10000}-\N{U+EFFFF}';
19 9         24 my $nameStartChar = $nameChar.'0-9\N{U+00B7}\N{U+0300}\N{U+036F}\N{U+203F}-\N{U+2040}-';
20 9         16 our $PREFIX = '[a-z][a-z0-9]*';
21 9         305 our $NAME = "[$nameStartChar][$nameChar]*";
22             }
23              
24 9     9   51 use constant localName => qr/^$NAME$/;
  9         15  
  9         1140  
25 9     9   46 use constant prefix => qr/^$PREFIX$/;
  9         11  
  9         787  
26 9     9   44 use constant qName => qr/^($PREFIX)_($NAME)$/;
  9         10  
  9         1110  
27 9     9   58 use constant blankNodeIdentifier => qr/^([a-zA-Z0-9]+)$/;
  9         19  
  9         621  
28 9     9   45 use constant blankNode => qr/^_:([a-zA-Z0-9]+)$/;
  9         13  
  9         700  
29 9     9   42 use constant IRIlike => qr/^[a-z][a-z0-9+.-]*:/;
  9         11  
  9         988  
30 9     9   47 use constant languageString => qr/^(.*)@([a-z]{2,8}(-[a-z0-9]{1,8})*)$/i;
  9         13  
  9         1552  
31 9     9   53 use constant languageTag => qr/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
  9         12  
  9         664  
32 9         1236 use constant datatypeString => qr/^(.*?)[\^]
33 9     9   52 ((($PREFIX)?_($NAME))|<([a-z][a-z0-9+.-]*:.*)>)$/x;
  9         26  
34              
35 9     9   55 use constant explicitIRIlike => qr/^<(.+)>$/;
  9         13  
  9         457  
36 9     9   43 use constant xsd_string => 'http://www.w3.org/2001/XMLSchema#string';
  9         11  
  9         21104  
37              
38             sub new {
39 87     87 0 38747 my ($class, %options) = @_;
40              
41 87   100     1701 my $self = bless {
      100        
      100        
      50        
42             ns => $options{ns},
43             complain => $options{complain} // 1,
44             strict => $options{strict} // 0,
45             null => $options{null}, # undef by default
46             bnode_prefix => $options{bnode_prefix} || 'b',
47             bnode_count => $options{bnode_count} || 0,
48             bnode_map => { },
49             }, $class;
50              
51             # facilitate use of this module together with RDF::Trine
52 87   100 0   455 my $callback = $options{callback} // sub { };
  0         0  
53 87 50 33     411 if (blessed $callback and $callback->isa('RDF::Trine::Model')) {
54 0         0 require RDF::Trine::Statement;
55 0         0 my $model = $callback;
56             $callback = sub {
57 0     0   0 eval {
58 0         0 $model->add_statement( trine_statement(@_) )
59             };
60 0 0       0 $self->error($@) if $@;
61 0         0 };
62             }
63 87         291 $self->{callback} = $callback;
64              
65 87         325 return $self;
66             }
67              
68             sub namespace_map { # sets the local namespace map
69 67     67 0 153 my ($self, $map) = @_;
70              
71             # TODO: copy on write because this is expensive!
72            
73             # copy default namespace map
74             # TODO: respect '_' and default map!
75 6         2339 my $ns = ref $self->{ns}
76 67 100       451 ? bless { %{$self->{ns}} }, 'RDF::NS'
77             : RDF::NS->new($self->{ns});
78              
79 67 100       1002051 if (ref $map) {
80 4 100       50 if (ref $map eq 'HASH') {
81 3         19 while (my ($prefix,$namespace) = each %$map) {
82 3 50       8 $prefix = '' if $prefix eq '_';
83 3 100       25 if ($prefix !~ prefix) {
    100          
84 1         4 $self->error("invalid prefix: $prefix");
85             } elsif ($namespace !~ IRIlike) {
86 1         4 $self->error("invalid namespace: $namespace");
87             } else {
88 1         5 $ns->{$prefix} = $namespace;
89             }
90             }
91             } else {
92 1         4 $self->error("namespace map must be map or string");
93             }
94             }
95              
96 64         301 $self->{ns} = $ns;
97             }
98              
99             sub decode {
100 67     67 1 5024 my ($self, $map, %options) = @_;
101              
102 67 50       173 unless ($options{keep_bnode_map}) {
103 67         147 $self->{bnode_map} = { };
104             }
105 67         136 $self->{visited} = { };
106              
107 67         246 $self->namespace_map( $map->{"_ns"} );
108              
109 64 100       686 if (exists $map->{_id}) {
110             # predicate map
111              
112 15         77 my $id = $map->{_id};
113 15 100       74 return if $self->is_null($id,'_id');
114              
115 13 50       63 my $subject = $id ne '' ? $self->expect_resource($id,'subject') : undef;
116 13 50 33     79 if (defined $subject and $subject ne '') {
    0          
117 13         40 $self->predicate_map( $subject, $map );
118             } elsif ($self->{strict}) {
119 0         0 $self->error("invalid subject", $id);
120             }
121              
122             } else {
123             # 3.4.1 subject maps
124 49 50       303 foreach my $key (grep { $_ ne '_' and $_ !~ /^_[^:]/ } keys %$map) {
  52         557  
125 51 100       213 next if $self->is_null($key,'subject');
126              
127 48         158 my $subject = $self->subject($key);
128 48 100       115 if (!$subject) {
129 4         10 $self->error("invalid subject", $key);
130 0         0 next;
131             }
132              
133 44         81 my $predicates = $map->{$key};
134 44 50 0     183 if (exists $predicates->{_id} and ($self->resource($predicates->{_id}) // '') ne $subject) {
      33        
135 0         0 $self->error("subject _id must be <$subject>");
136             } else {
137 44         149 $self->predicate_map( $subject, $predicates );
138             }
139             }
140             }
141             }
142              
143             sub predicate_map {
144 61     61 0 94 my ($self, $subject, $map) = @_;
145              
146 61         372 $self->{visited}{refaddr $map} = 1;
147              
148 61         288 for (keys %$map) {
149 72 100 66     401 next if $_ eq '_id' or $_ eq '_ns';
150              
151 57 50       80 my $predicate = do {
152 57 100       344 if ($_ eq 'a') {
    100          
    100          
    100          
153 18         62 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
154             } elsif ( $_ =~ /^<(.+)>$/ ) {
155 20         56 $self->iri($1);
156             } elsif ( $_ =~ qName ) {
157 16         59 $self->prefixed_name($1,$2);
158             } elsif ( $_ =~ IRIlike ) {
159 2         5 $self->iri($_);
160             } else {
161 1 50 33     10 $self->error("invalid predicate IRI $_")
162             if $_ ne '' or $self->{strict};
163 0         0 next;
164             }
165             } or next;
166              
167 56         129 my $value = $map->{$_};
168              
169             # encoded_object
170 56 100       165 foreach my $o (ref $value eq 'ARRAY' ? @$value : $value) {
171 59 100       110 if ($self->is_null($o,'object')) {
    100          
    100          
172 5         16 next;
173             } elsif (!ref $o) {
174 48 50       137 if (my $object = $self->object($o)) {
175 48         210 $self->triple( $subject, $predicate, $object );
176             }
177 48         737 next;
178             } elsif (ref $o eq 'HASH') {
179 4 100 50     68 my $object = exists $o->{_id}
180             ? ($self->expect_resource($o->{_id},'object _id') // next)
181             : $self->blank_identifier();
182              
183 4         17 $self->triple( $subject, $predicate, [$object] );
184              
185 4 50 33     77 unless( ref $object and $self->{visited}{refaddr $object} ) {
186 4         12 $self->predicate_map( $object, $o );
187             }
188             } else {
189 2         6 $self->error('object must not be reference to '.ref $o);
190             }
191             }
192             }
193             }
194              
195             sub is_null {
196 125     125 0 189 my ($self, $value, $check) = @_;
197              
198 125 100 100     725 if ( !defined $value or (defined $self->{null} and $value eq $self->{null} ) ) {
      66        
199 10 100 66     151 if ($check and $self->{strict}) {
200 1         5 $self->error("$check must not be null")
201             }
202 9         44 1;
203             } else {
204 115         373 0;
205             }
206             }
207              
208             sub expect_resource {
209 15     15 0 29 my ($self, $r, $expect) = @_;
210 15 50       45 if (my $resource = $self->resource($r)) {
211 15         34 return $resource;
212             } else {
213 0 0       0 if (!$self->is_null($r, $expect)) {
214 0 0       0 $expect .= ": " . (ref $r ? reftype $r : $r);
215 0         0 $self->error("invalid $expect");
216             }
217 0         0 return;
218             }
219             }
220              
221             sub resource {
222 53     53 0 59 my ($self, $r) = @_;
223            
224 53 50       99 return unless defined $r;
225              
226 53 100       320 if ( $r =~ explicitIRIlike ) {
    100          
    50          
    100          
227 16         29 $self->iri($1);
228             } elsif ( $r =~ blankNode ) {
229 2         7 $self->blank_identifier($1);
230             } elsif ( $r =~ qName ) {
231 0         0 $self->prefixed_name($1,$2);
232             } elsif ( $r =~ IRIlike ) {
233 32         73 $self->iri($r);
234             } else {
235             undef
236 3         6 }
237             }
238              
239             sub subject { # plain IRI, qName, or blank node
240 48     48 1 76 my ($self, $s) = @_;
241              
242 48 50       141 return unless defined $s;
243              
244 48 100       326 if ( $s =~ IRIlike ) {
    100          
    100          
245 42         164 $self->iri($s);
246             } elsif ( $s =~ qName ) {
247 1         4 $self->prefixed_name($1,$2);
248             } elsif ( $s =~ blankNode ) {
249 1         3 $self->blank_identifier($1);
250             } else {
251             undef
252 4         6 }
253             }
254              
255             sub object {
256 113     113 1 143 my ($self, $o) = @_;
257              
258 113 50       1179 if (!defined $o) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
259 0         0 undef;
260             } elsif ( $o =~ explicitIRIlike ) {
261 6         15 [$self->iri($1)];
262             } elsif ( $o =~ blankNode ) {
263 1         4 [$self->blank_identifier($1)];
264             } elsif ( $o =~ qName ) {
265 16         35 [$self->prefixed_name($1,$2)];
266             } elsif ( $o =~ languageString ) {
267 21         85 [$1, lc($2)];
268             } elsif ( $o =~ /^(.*)[@]$/ ) {
269 22         109 [$1, undef];
270             } elsif ( $o =~ datatypeString ) {
271 13 100       48 if ($6) {
272 5   50     14 my $datatype = $self->iri($6) // return;
273 5 100       15 if ($datatype eq xsd_string) {
274 2         10 [$1,undef];
275             } else {
276 3         17 [$1,undef,$datatype];
277             }
278             } else {
279 8   50     30 my $datatype = $self->prefixed_name($4,$5) // return;
280 8 100       25 if ($datatype eq xsd_string) {
281 3         18 [$1,undef];
282             } else {
283 5         31 [$1,undef,$datatype];
284             }
285             }
286             } elsif ( $o =~ IRIlike ) {
287 25         61 [$self->iri($o)];
288             } else {
289 9         36 [$o, undef];
290             }
291             }
292              
293             sub plain_literal {
294 6     6 0 1337 my ($self, $object) = @_;
295 6         10 my $obj = $self->object($object);
296 6 100       17 return if @$obj == 1; # resource or blank
297 4         12 return $obj->[0];
298             }
299              
300             sub iri {
301 195     195 0 319 my ($self, $iri) = @_;
302             # TODO: check full RFC form of IRIs
303 195 50       588 if ( $iri !~ IRIlike ) {
304 0         0 return $self->error("invalid IRI $iri");
305             } else {
306 195         671 return $iri;
307             }
308             }
309              
310             sub prefixed_name {
311 47     47 0 191 my ($self, $prefix, $name) = @_;
312 47 0 50     208 my $base = $self->{ns}{$prefix // ''}
      0        
      50        
313             // return $self->error(
314             $prefix // '' ne ''
315             ? "unknown prefix: $prefix" : "not an URI: $name");
316 47         145 $self->iri($base.$name);
317             }
318              
319             sub triple {
320 52     52 0 98 my $self = shift;
321 52 100       148 my $subject = ref $_[0] ? '_:'.${$_[0]} : $_[0];
  1         3  
322 52         64 my $predicate = $_[1];
323 52         57 my @object = @{$_[2]};
  52         134  
324 52 100       118 $object[0] = '_:'.${$object[0]} if ref $object[0];
  5         10  
325 52         247 $self->{callback}->($subject, $predicate, @object);
326             }
327              
328             sub error {
329 11     11 0 16 my ($self, $message, $value, $context) = @_;
330              
331             # TODO: include $context (bless $message, 'RDF::aREF::Error')
332              
333 11 100       21 if (defined $value) {
334 4 50       13 $message .= ': ' . (ref $value ? reftype $value : $value);
335             }
336            
337 11 50       27 if (!$self->{complain}) {
    50          
338 0         0 return;
339             } elsif ($self->{complain} == 1) {
340 0         0 carp $message;
341             } else {
342 11         1359 croak $message;
343             }
344             }
345              
346             sub bnode_count {
347 0 0   0 1 0 $_[0]->{bnode_count} = $_[1] if @_ > 1;
348 0         0 $_[0]->{bnode_count};
349             }
350              
351             # TODO: test this
352             sub blank_identifier {
353 6     6 0 14 my ($self, $id) = @_;
354              
355 6         8 my $bnode;
356 6 100       9 if ( defined $id ) {
357 4   66     28 $bnode = ($self->{bnode_map}{$id} //= $self->{bnode_prefix} . ++$self->{bnode_count});
358             } else {
359 2         9 $bnode = $self->{bnode_prefix} . ++$self->{bnode_count};
360             }
361              
362 6         20 return \$bnode;
363             }
364              
365             sub clean_bnodes {
366 0     0 1   my ($self) = @_;
367 0           $self->{bnode_count} = 0;
368 0           $self->{bnode_map} = {};
369             }
370              
371             # TODO: test this
372             sub trine_statement {
373             RDF::Trine::Statement->new(
374             # subject
375             (substr($_[0],0,2) eq '_:' ? RDF::Trine::Node::Blank->new(substr $_[0], 2)
376             : RDF::Trine::Node::Resource->new($_[0])),
377             # predicate
378             RDF::Trine::Node::Resource->new($_[1]),
379             # object
380 0 0   0 0   do {
381 0 0         if (@_ == 3) {
382 0 0         if (substr($_[2],0,2) eq '_:') {
383 0           RDF::Trine::Node::Blank->new(substr $_[2], 2);
384             } else {
385 0           RDF::Trine::Node::Resource->new($_[2]);
386             }
387             } else {
388 0           RDF::Trine::Node::Literal->new($_[2],$_[3],$_[4]);
389             }
390             }
391             );
392             }
393              
394              
395             1;
396             __END__
397              
398             =head1 NAME
399              
400             RDF::aREF::Decoder - decode another RDF Encoding Form (to RDF triples)
401              
402             =head1 SYNOPSIS
403              
404             use RDF::aREF::Decoder;
405              
406             RDF::aREF::Decoder->new( %options )->decode( $aref );
407              
408             =head1 DESCRIPTION
409              
410             This module implements a decoder from another RDF Encoding Form (aREF), given
411             as in form of Perl arrays, hashes, and Unicode strings, to RDF triples.
412              
413             =head1 OPTIONS
414              
415             =head2 ns
416              
417             A default namespace map, given either as hash reference or as version string of
418             module L<RDF::NS>. Set to the most recent version of RDF::NS by default, but relying
419             on a default value is not recommended!
420              
421             =head2 callback
422              
423             A code reference that is called for each triple with a list of three to five
424             elements:
425              
426             =over
427              
428             =item subject
429              
430             The subject IRI or subject blank node as string. Blank nodes always start with
431             C<_:>.
432              
433             =item predicate
434              
435             The predicate IRI.
436              
437             =item object
438              
439             The object IRI or object blank node or literal object as string. Blank nodes
440             always start with C<_:> and literal objects can be detected by existence of the
441             (possibly empty) language or datatype element.
442              
443             =item language
444              
445             The language tag (possible the empty string) for literal objects.
446              
447             =item datatype
448              
449             The object's datatype if object is a literal and datatype is not
450             C<http://www.w3.org/2001/XMLSchema#string>.
451              
452             =back
453              
454             For convenience an instance of L<RDF::Trine::Model> can also be used as
455             callback.
456              
457             =head2 complain
458              
459             What to do on errors. Set to 1 be default (warn). Set to 0 to ignore. Other
460             values will die on errors.
461              
462             =head2 strict
463              
464             Enables errors on undefined subjects, predicates, and objects. By default
465             the Perl value C<undef> in any part of an encoded RDF triple will silently
466             ignore the triple, so aREF structures can easily be used as templates with
467             optional values.
468              
469             =head2 null
470              
471             A null object that is treated equivalent to C<undef> if found as object. For
472             instance setting this to the empty string will ignore all triples with the
473             empty string as literal value.
474              
475             =head2 bnode_prefix
476              
477             A prefix for blank node identifiers. Defaults to "b", so blank node identifiers
478             will be "b1", "b2", "b3" etc.
479              
480             =head2 bnode_count
481              
482             An integer to start creating blank node identifiers with. The default value "0"
483             results in blank node identifiers starting from "b1". This option can be useful
484             to avoid collision of blank node identifiers when merging multiple aREF
485             instances. The current counter value is accessible as accessor.
486              
487             =head1 METHODS
488              
489             =head2 decode( $aref [, keep_bnode_map => 1 ] )
490              
491             Encode RDF data given in aREF. Resets all blank node identifier mappings unless
492             option c<keep_bnode_map> is set.
493              
494             =head2 clean_bnodes
495              
496             Delete blank node identifier mapping and reset bnode_count.
497              
498             =head1 EXPORTABLE CONSTANTS
499              
500             On request this module exports the following regular expressions, as defined in the
501             L<aREF specification|http://gbv.github.io/aREF/aREF.html>:
502              
503             =over
504              
505             =item qName
506              
507             =item blankNode
508              
509             =item IRIlike
510              
511             =item languageString
512              
513             =item languageTag
514              
515             =item datatypeString
516              
517             =back
518              
519             =head1 SEE ALSO
520              
521             L<RDF::aREF::Encoder>
522              
523             =cut