File Coverage

blib/lib/RDF/aREF/Decoder.pm
Criterion Covered Total %
statement 212 220 96.3
branch 128 142 90.1
condition 38 54 70.3
subroutine 37 39 94.8
pod 5 18 27.7
total 420 473 88.7


line stmt bran cond sub pod time code
1             package RDF::aREF::Decoder;
2 10     10   273504 use strict;
  10         24  
  10         293  
3 10     10   44 use warnings;
  10         18  
  10         208  
4 10     10   90 use v5.10;
  10         32  
5              
6             our $VERSION = '0.27';
7              
8 10     10   3658 use RDF::NS;
  10         202440  
  10         294  
9 10     10   70 use Carp qw(croak carp);
  10         20  
  10         461  
10 10     10   54 use Scalar::Util qw(refaddr reftype blessed);
  10         17  
  10         432  
11              
12 10     10   52 use parent 'Exporter';
  10         20  
  10         74  
13             our @EXPORT_OK = qw(prefix localName qName blankNode blankNodeIdentifier
14             IRIlike languageString languageTag datatypeString);
15              
16             our ($PREFIX, $NAME);
17             BEGIN {
18 10     10   1068 my $nameStartChar = '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 10         29 my $nameChar = $nameStartChar.'0-9\-\.\N{U+00B7}\N{U+0300}-\N{U+036F}\N{U+203F}-\N{U+2040}';
20 10         30 our $PREFIX = '[a-z][a-z0-9]*';
21 10         367 our $NAME = "[$nameStartChar][$nameChar]*";
22             }
23              
24 10     10   68 use constant localName => qr/^$NAME$/;
  10         18  
  10         1158  
25 10     10   58 use constant prefix => qr/^$PREFIX$/;
  10         24  
  10         837  
26 10     10   59 use constant qName => qr/^($PREFIX)_($NAME)$/;
  10         25  
  10         1206  
27 10     10   67 use constant blankNodeIdentifier => qr/^([a-zA-Z0-9]+)$/;
  10         15  
  10         670  
28 10     10   69 use constant blankNode => qr/^_:([a-zA-Z0-9]+)$/;
  10         36  
  10         674  
29 10     10   62 use constant IRIlike => qr/^[a-z][a-z0-9+.-]*:/;
  10         19  
  10         959  
30 10     10   63 use constant languageString => qr/^(.*)@([a-z]{2,8}(-[a-z0-9]{1,8})*)$/i;
  10         62  
  10         824  
31 10     10   80 use constant languageTag => qr/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
  10         16  
  10         735  
32 10         1193 use constant datatypeString => qr/^(.*?)[\^]
33 10     10   56 ((($PREFIX)?_($NAME))|<([a-z][a-z0-9+.-]*:.*)>)$/x;
  10         17  
34              
35 10     10   73 use constant explicitIRIlike => qr/^<(.+)>$/;
  10         18  
  10         456  
36 10     10   52 use constant xsd_string => 'http://www.w3.org/2001/XMLSchema#string';
  10         21  
  10         20694  
37              
38             sub new {
39 120     120 0 115603 my ($class, %options) = @_;
40              
41             my $self = bless {
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 120   100     1944 bnode_count => $options{bnode_count} || 0,
      100        
      100        
      50        
48             bnode_map => { },
49             }, $class;
50              
51             # facilitate use of this module together with RDF::Trine
52 120   100 0   563 my $callback = $options{callback} // sub { };
53 120 100 66     613 if (blessed $callback and $callback->isa('RDF::Trine::Model')) {
54 5         71 require RDF::Trine::Statement;
55 5         12 my $model = $callback;
56             $callback = sub {
57 12     12   19 eval {
58 12         56 $model->add_statement( trine_statement(@_) )
59             };
60 12 100       22235 $self->error($@) if $@;
61 5         27 };
62             }
63 120         390 $self->{callback} = $callback;
64              
65 120         424 return $self;
66             }
67              
68             sub namespace_map { # sets the local namespace map
69 102     102 0 322 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             my $ns = ref $self->{ns}
76 10         6071 ? bless { %{$self->{ns}} }, 'RDF::NS'
77 102 100       645 : RDF::NS->new($self->{ns});
78              
79 102 100       2820681 if (ref $map) {
80 4 100       19 if (ref $map eq 'HASH') {
81 3         23 while (my ($prefix,$namespace) = each %$map) {
82 3 50       9 $prefix = '' if $prefix eq '_';
83 3 100       27 if ($prefix !~ prefix) {
    100          
84 1         5 $self->error("invalid prefix: $prefix");
85             } elsif ($namespace !~ IRIlike) {
86 1         5 $self->error("invalid namespace: $namespace");
87             } else {
88 1         6 $ns->{$prefix} = $namespace;
89             }
90             }
91             } else {
92 1         6 $self->error("namespace map must be map or string");
93             }
94             }
95              
96 99         484 $self->{ns} = $ns;
97             }
98              
99             sub decode {
100 102     102 1 5435 my ($self, $map, %options) = @_;
101              
102 102 50       283 unless ($options{keep_bnode_map}) {
103 102         247 $self->{bnode_map} = { };
104             }
105 102         197 $self->{visited} = { };
106              
107 102         472 $self->namespace_map( $map->{"_ns"} );
108              
109 99 100       1278 if (exists $map->{_id}) {
110             # predicate map
111              
112 30         85 my $id = $map->{_id};
113 30 100       136 return if $self->is_null($id,'_id');
114              
115 27 100       130 my $subject = $id ne '' ? $self->expect_resource($id,'subject') : undef;
116 23 100 66     108 if (defined $subject and $subject ne '') {
    50          
117 22         74 $self->predicate_map( $subject, $map );
118             } elsif ($self->{strict}) {
119 1         5 $self->error("invalid subject", $id);
120             }
121              
122             } else {
123             # 3.4.1 subject maps
124 69 50       386 foreach my $key (grep { $_ ne '_' and $_ !~ /^_[^:]/ } keys %$map) {
  73         725  
125 72 100       363 next if $self->is_null($key,'subject');
126              
127 69         290 my $subject = $self->subject($key);
128 68 100       179 if (!$subject) {
129 7         29 $self->error("invalid subject", $key);
130 0         0 next;
131             }
132              
133 61         113 my $predicates = $map->{$key};
134 61 100 100     202 if (exists $predicates->{_id} and ($self->resource($predicates->{_id}) // '') ne $subject) {
      66        
135 3         16 $self->error("subject _id must be <$subject>");
136             } else {
137 58         199 $self->predicate_map( $subject, $predicates );
138             }
139             }
140             }
141             }
142              
143             sub predicate_map {
144 87     87 0 222 my ($self, $subject, $map) = @_;
145              
146 87         509 $self->{visited}{refaddr $map} = 1;
147              
148 87         345 for (keys %$map) {
149 109 100 66     573 next if $_ eq '_id' or $_ eq '_ns';
150              
151 85 50       129 my $predicate = do {
152 85 100       452 if ($_ eq 'a') {
    100          
    100          
    100          
153 31         97 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
154             } elsif ( $_ =~ /^<(.+)>$/ ) {
155 21         51 $self->iri($1);
156             } elsif ( $_ =~ qName ) {
157 27         99 $self->prefixed_name($1,$2);
158             } elsif ( $_ =~ IRIlike ) {
159 4         12 $self->iri($_);
160             } else {
161             $self->error("invalid predicate IRI $_")
162 2 50 33     20 if $_ ne '' or $self->{strict};
163 0         0 next;
164             }
165             } or next;
166              
167 81         168 my $value = $map->{$_};
168              
169             # encoded_object
170 81 100       245 foreach my $o (ref $value eq 'ARRAY' ? @$value : $value) {
171 87 100       211 if ($self->is_null($o,'object')) {
    100          
    100          
172 5         13 next;
173             } elsif (!ref $o) {
174 68 50       208 if (my $object = $self->object($o)) {
175 67         237 $self->triple( $subject, $predicate, $object );
176             }
177 67         18883 next;
178             } elsif (ref $o eq 'HASH') {
179             my $object = exists $o->{_id}
180 10 100 50     54 ? ($self->expect_resource($o->{_id},'object _id') // next)
181             : $self->blank_identifier();
182              
183 7         31 $self->triple( $subject, $predicate, [$object] );
184              
185 7 50 33     122 unless( ref $object and $self->{visited}{refaddr $object} ) {
186 7         78 $self->predicate_map( $object, $o );
187             }
188             } else {
189 2         8 $self->error('object must not be reference to '.ref $o);
190             }
191             }
192             }
193             }
194              
195             sub is_null {
196 194     194 0 437 my ($self, $value, $check) = @_;
197              
198 194 100 100     998 if ( !defined $value or (defined $self->{null} and $value eq $self->{null} ) ) {
      100        
199 13 100 66     63 if ($check and $self->{strict}) {
200 4         22 $self->error("$check must not be null")
201             }
202 9         34 1;
203             } else {
204 181         622 0;
205             }
206             }
207              
208             sub expect_resource {
209 31     31 0 88 my ($self, $r, $expect) = @_;
210 31 100       249 if (my $resource = $self->resource($r)) {
211 24         64 return $resource;
212             } else {
213 5 50       19 if (!$self->is_null($r, $expect)) {
214 5 100       30 $expect .= ": " . (ref $r ? reftype $r : $r);
215 5         21 $self->error("invalid $expect");
216             }
217 0         0 return;
218             }
219             }
220              
221             sub resource {
222 72     72 0 155 my ($self, $r) = @_;
223            
224 72 100       166 return unless defined $r;
225              
226 71 100       456 if ( $r =~ explicitIRIlike ) {
    100          
    100          
    100          
227 21         56 $self->iri($1);
228             } elsif ( $r =~ blankNode ) {
229 2         8 $self->blank_identifier($1);
230             } elsif ( $r =~ qName ) {
231 2         11 $self->prefixed_name($1,$2);
232             } elsif ( $r =~ IRIlike ) {
233 38         116 $self->iri($r);
234             } else {
235             undef
236 8         24 }
237             }
238              
239             sub subject { # plain IRI, qName, or blank node
240 69     69 1 160 my ($self, $s) = @_;
241              
242 69 50       169 return unless defined $s;
243              
244 69 100       497 if ( $s =~ IRIlike ) {
    100          
    100          
245 57         281 $self->iri($s);
246             } elsif ( $s =~ qName ) {
247 2         11 $self->prefixed_name($1,$2);
248             } elsif ( $s =~ blankNode ) {
249 3         12 $self->blank_identifier($1);
250             } else {
251             undef
252 7         15 }
253             }
254              
255             sub object {
256 133     133 1 272 my ($self, $o) = @_;
257              
258 133 50       1099 if (!defined $o) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
259 0         0 undef;
260             } elsif ( $o =~ explicitIRIlike ) {
261 7         23 [$self->iri($1)];
262             } elsif ( $o =~ blankNode ) {
263 3         10 [$self->blank_identifier($1)];
264             } elsif ( $o =~ qName ) {
265 21         62 [$self->prefixed_name($1,$2)];
266             } elsif ( $o =~ languageString ) {
267 22         96 [$1, lc($2)];
268             } elsif ( $o =~ /^(.*)[@]$/ ) {
269 23         108 [$1, undef];
270             } elsif ( $o =~ datatypeString ) {
271 16 100       62 if ($6) {
272 6   50     19 my $datatype = $self->iri($6) // return;
273 6 100       18 if ($datatype eq xsd_string) {
274 2         11 [$1,undef];
275             } else {
276 4         21 [$1,undef,$datatype];
277             }
278             } else {
279 10   50     35 my $datatype = $self->prefixed_name($4,$5) // return;
280 9 100       31 if ($datatype eq xsd_string) {
281 3         13 [$1,undef];
282             } else {
283 6         36 [$1,undef,$datatype];
284             }
285             }
286             } elsif ( $o =~ IRIlike ) {
287 29         72 [$self->iri($o)];
288             } else {
289 12         48 [$o, undef];
290             }
291             }
292              
293             sub plain_literal {
294 6     6 0 2073 my ($self, $object) = @_;
295 6         12 my $obj = $self->object($object);
296 6 100       18 return if @$obj == 1; # resource or blank
297 4         12 return $obj->[0];
298             }
299              
300             sub iri {
301 246     246 0 596 my ($self, $iri) = @_;
302             # TODO: check full RFC form of IRIs
303 246 100       747 if ( $iri !~ IRIlike ) {
304 1         5 return $self->error("invalid IRI $iri");
305             } else {
306 245         794 return $iri;
307             }
308             }
309              
310             sub prefixed_name {
311 68     68 0 339 my ($self, $prefix, $name) = @_;
312 68 50 50     336 my $base = $self->{ns}{$prefix // ''}
      50        
      100        
313             // return $self->error(
314             $prefix // '' ne ''
315             ? "unknown prefix: $prefix" : "not an URI: $name");
316 63         222 $self->iri($base.$name);
317             }
318              
319             sub triple {
320 74     74 0 153 my $self = shift;
321 74 100       174 my $subject = ref $_[0] ? '_:'.${$_[0]} : $_[0];
  6         17  
322 74         120 my $predicate = $_[1];
323 74         99 my @object = @{$_[2]};
  74         244  
324 74 100       181 $object[0] = '_:'.${$object[0]} if ref $object[0];
  10         36  
325 74         320 $self->{callback}->($subject, $predicate, @object);
326             }
327              
328             sub error {
329 34     34 0 124 my ($self, $message, $value, $context) = @_;
330              
331             # TODO: include $context (bless $message, 'RDF::aREF::Error')
332              
333 34 100       81 if (defined $value) {
334 8 50       31 $message .= ': ' . (ref $value ? reftype $value : $value);
335             }
336            
337 34 50       191 if (!$self->{complain}) {
    100          
338 0         0 return;
339             } elsif ($self->{complain} == 1) {
340 1         43 carp $message;
341             } else {
342 33         5217 croak $message;
343             }
344             }
345              
346             sub bnode_count {
347 2 100   2 1 824 $_[0]->{bnode_count} = $_[1] if @_ > 1;
348 2         8 $_[0]->{bnode_count};
349             }
350              
351             # TODO: test this
352             sub blank_identifier {
353 13     13 0 38 my ($self, $id) = @_;
354              
355 13         37 my $bnode;
356 13 100       71 if ( defined $id ) {
357 8   66     53 $bnode = ($self->{bnode_map}{$id} //= $self->{bnode_prefix} . ++$self->{bnode_count});
358             } else {
359 5         17 $bnode = $self->{bnode_prefix} . ++$self->{bnode_count};
360             }
361              
362 13         111 return \$bnode;
363             }
364              
365             sub clean_bnodes {
366 0     0 1 0 my ($self) = @_;
367 0         0 $self->{bnode_count} = 0;
368 0         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 22 100   22 0 358 do {
381 22 100       978 if (@_ == 3) {
382 15 100       45 if (substr($_[2],0,2) eq '_:') {
383 5         26 RDF::Trine::Node::Blank->new(substr $_[2], 2);
384             } else {
385 10         25 RDF::Trine::Node::Resource->new($_[2]);
386             }
387             } else {
388 7         51 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