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   93384 use strict;
  10         17  
  10         223  
3 10     10   41 use warnings;
  10         16  
  10         172  
4 10     10   85 use v5.10;
  10         58  
5              
6             our $VERSION = '0.27';
7              
8 10     10   2294 use RDF::NS;
  10         67080  
  10         242  
9 10     10   52 use Carp qw(croak carp);
  10         20  
  10         462  
10 10     10   50 use Scalar::Util qw(refaddr reftype blessed);
  10         17  
  10         403  
11              
12 10     10   2031 use parent 'Exporter';
  10         1929  
  10         54  
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   945 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 10         25 my $nameStartChar = $nameChar.'0-9\N{U+00B7}\N{U+0300}\N{U+036F}\N{U+203F}-\N{U+2040}-';
20 10         17 our $PREFIX = '[a-z][a-z0-9]*';
21 10         278 our $NAME = "[$nameStartChar][$nameChar]*";
22             }
23              
24 10     10   57 use constant localName => qr/^$NAME$/;
  10         24  
  10         1017  
25 10     10   59 use constant prefix => qr/^$PREFIX$/;
  10         17  
  10         759  
26 10     10   65 use constant qName => qr/^($PREFIX)_($NAME)$/;
  10         17  
  10         1111  
27 10     10   58 use constant blankNodeIdentifier => qr/^([a-zA-Z0-9]+)$/;
  10         14  
  10         547  
28 10     10   50 use constant blankNode => qr/^_:([a-zA-Z0-9]+)$/;
  10         16  
  10         578  
29 10     10   53 use constant IRIlike => qr/^[a-z][a-z0-9+.-]*:/;
  10         15  
  10         764  
30 10     10   58 use constant languageString => qr/^(.*)@([a-z]{2,8}(-[a-z0-9]{1,8})*)$/i;
  10         17  
  10         691  
31 10     10   50 use constant languageTag => qr/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
  10         25  
  10         605  
32 10         1115 use constant datatypeString => qr/^(.*?)[\^]
33 10     10   50 ((($PREFIX)?_($NAME))|<([a-z][a-z0-9+.-]*:.*)>)$/x;
  10         24  
34              
35 10     10   58 use constant explicitIRIlike => qr/^<(.+)>$/;
  10         14  
  10         446  
36 10     10   49 use constant xsd_string => 'http://www.w3.org/2001/XMLSchema#string';
  10         27  
  10         17460  
37              
38             sub new {
39 120     120 0 107327 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     1569 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   506 my $callback = $options{callback} // sub { };
53 120 100 66     496 if (blessed $callback and $callback->isa('RDF::Trine::Model')) {
54 5         37 require RDF::Trine::Statement;
55 5         9 my $model = $callback;
56             $callback = sub {
57 12     12   16 eval {
58 12         30 $model->add_statement( trine_statement(@_) )
59             };
60 12 100       19638 $self->error($@) if $@;
61 5         26 };
62             }
63 120         364 $self->{callback} = $callback;
64              
65 120         384 return $self;
66             }
67              
68             sub namespace_map { # sets the local namespace map
69 102     102 0 293 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 8         3699 ? bless { %{$self->{ns}} }, 'RDF::NS'
77 102 100       567 : RDF::NS->new($self->{ns});
78              
79 102 100       2560287 if (ref $map) {
80 4 100       15 if (ref $map eq 'HASH') {
81 3         19 while (my ($prefix,$namespace) = each %$map) {
82 3 50       9 $prefix = '' if $prefix eq '_';
83 3 100       24 if ($prefix !~ prefix) {
    100          
84 1         4 $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         332 $self->{ns} = $ns;
97             }
98              
99             sub decode {
100 102     102 1 4927 my ($self, $map, %options) = @_;
101              
102 102 50       253 unless ($options{keep_bnode_map}) {
103 102         216 $self->{bnode_map} = { };
104             }
105 102         192 $self->{visited} = { };
106              
107 102         371 $self->namespace_map( $map->{"_ns"} );
108              
109 99 100       1090 if (exists $map->{_id}) {
110             # predicate map
111              
112 30         66 my $id = $map->{_id};
113 30 100       115 return if $self->is_null($id,'_id');
114              
115 27 100       143 my $subject = $id ne '' ? $self->expect_resource($id,'subject') : undef;
116 23 100 66     102 if (defined $subject and $subject ne '') {
    50          
117 22         79 $self->predicate_map( $subject, $map );
118             } elsif ($self->{strict}) {
119 1         4 $self->error("invalid subject", $id);
120             }
121              
122             } else {
123             # 3.4.1 subject maps
124 69 50       346 foreach my $key (grep { $_ ne '_' and $_ !~ /^_[^:]/ } keys %$map) {
  73         668  
125 72 100       260 next if $self->is_null($key,'subject');
126              
127 69         187 my $subject = $self->subject($key);
128 68 100       243 if (!$subject) {
129 7         25 $self->error("invalid subject", $key);
130 0         0 next;
131             }
132              
133 61         150 my $predicates = $map->{$key};
134 61 100 100     168 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         142 $self->predicate_map( $subject, $predicates );
138             }
139             }
140             }
141             }
142              
143             sub predicate_map {
144 87     87 0 178 my ($self, $subject, $map) = @_;
145              
146 87         436 $self->{visited}{refaddr $map} = 1;
147              
148 87         286 for (keys %$map) {
149 109 100 66     449 next if $_ eq '_id' or $_ eq '_ns';
150              
151 85 50       122 my $predicate = do {
152 85 100       371 if ($_ eq 'a') {
    100          
    100          
    100          
153 31         82 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
154             } elsif ( $_ =~ /^<(.+)>$/ ) {
155 21         41 $self->iri($1);
156             } elsif ( $_ =~ qName ) {
157 27         86 $self->prefixed_name($1,$2);
158             } elsif ( $_ =~ IRIlike ) {
159 4         9 $self->iri($_);
160             } else {
161             $self->error("invalid predicate IRI $_")
162 2 50 33     16 if $_ ne '' or $self->{strict};
163 0         0 next;
164             }
165             } or next;
166              
167 81         148 my $value = $map->{$_};
168              
169             # encoded_object
170 81 100       264 foreach my $o (ref $value eq 'ARRAY' ? @$value : $value) {
171 87 100       210 if ($self->is_null($o,'object')) {
    100          
    100          
172 5         11 next;
173             } elsif (!ref $o) {
174 68 50       172 if (my $object = $self->object($o)) {
175 67         193 $self->triple( $subject, $predicate, $object );
176             }
177 67         18594 next;
178             } elsif (ref $o eq 'HASH') {
179             my $object = exists $o->{_id}
180 10 100 50     42 ? ($self->expect_resource($o->{_id},'object _id') // next)
181             : $self->blank_identifier();
182              
183 7         27 $self->triple( $subject, $predicate, [$object] );
184              
185 7 50 33     118 unless( ref $object and $self->{visited}{refaddr $object} ) {
186 7         26 $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 194     194 0 443 my ($self, $value, $check) = @_;
197              
198 194 100 100     890 if ( !defined $value or (defined $self->{null} and $value eq $self->{null} ) ) {
      100        
199 13 100 66     59 if ($check and $self->{strict}) {
200 4         22 $self->error("$check must not be null")
201             }
202 9         24 1;
203             } else {
204 181         534 0;
205             }
206             }
207              
208             sub expect_resource {
209 31     31 0 69 my ($self, $r, $expect) = @_;
210 31 100       89 if (my $resource = $self->resource($r)) {
211 24         88 return $resource;
212             } else {
213 5 50       13 if (!$self->is_null($r, $expect)) {
214 5 100       23 $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 135 my ($self, $r) = @_;
223            
224 72 100       192 return unless defined $r;
225              
226 71 100       379 if ( $r =~ explicitIRIlike ) {
    100          
    100          
    100          
227 21         51 $self->iri($1);
228             } elsif ( $r =~ blankNode ) {
229 2         66 $self->blank_identifier($1);
230             } elsif ( $r =~ qName ) {
231 2         8 $self->prefixed_name($1,$2);
232             } elsif ( $r =~ IRIlike ) {
233 38         119 $self->iri($r);
234             } else {
235             undef
236 8         23 }
237             }
238              
239             sub subject { # plain IRI, qName, or blank node
240 69     69 1 137 my ($self, $s) = @_;
241              
242 69 50       154 return unless defined $s;
243              
244 69 100       356 if ( $s =~ IRIlike ) {
    100          
    100          
245 57         179 $self->iri($s);
246             } elsif ( $s =~ qName ) {
247 2         9 $self->prefixed_name($1,$2);
248             } elsif ( $s =~ blankNode ) {
249 3         14 $self->blank_identifier($1);
250             } else {
251             undef
252 7         15 }
253             }
254              
255             sub object {
256 133     133 1 267 my ($self, $o) = @_;
257              
258 133 50       966 if (!defined $o) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
259 0         0 undef;
260             } elsif ( $o =~ explicitIRIlike ) {
261 7         20 [$self->iri($1)];
262             } elsif ( $o =~ blankNode ) {
263 3         8 [$self->blank_identifier($1)];
264             } elsif ( $o =~ qName ) {
265 21         63 [$self->prefixed_name($1,$2)];
266             } elsif ( $o =~ languageString ) {
267 22         132 [$1, lc($2)];
268             } elsif ( $o =~ /^(.*)[@]$/ ) {
269 23         96 [$1, undef];
270             } elsif ( $o =~ datatypeString ) {
271 16 100       54 if ($6) {
272 6   50     14 my $datatype = $self->iri($6) // return;
273 6 100       18 if ($datatype eq xsd_string) {
274 2         9 [$1,undef];
275             } else {
276 4         17 [$1,undef,$datatype];
277             }
278             } else {
279 10   50     28 my $datatype = $self->prefixed_name($4,$5) // return;
280 9 100       22 if ($datatype eq xsd_string) {
281 3         12 [$1,undef];
282             } else {
283 6         74 [$1,undef,$datatype];
284             }
285             }
286             } elsif ( $o =~ IRIlike ) {
287 29         62 [$self->iri($o)];
288             } else {
289 12         44 [$o, undef];
290             }
291             }
292              
293             sub plain_literal {
294 6     6 0 2089 my ($self, $object) = @_;
295 6         11 my $obj = $self->object($object);
296 6 100       16 return if @$obj == 1; # resource or blank
297 4         12 return $obj->[0];
298             }
299              
300             sub iri {
301 246     246 0 494 my ($self, $iri) = @_;
302             # TODO: check full RFC form of IRIs
303 246 100       744 if ( $iri !~ IRIlike ) {
304 1         6 return $self->error("invalid IRI $iri");
305             } else {
306 245         763 return $iri;
307             }
308             }
309              
310             sub prefixed_name {
311 68     68 0 318 my ($self, $prefix, $name) = @_;
312 68 50 50     300 my $base = $self->{ns}{$prefix // ''}
      50        
      100        
313             // return $self->error(
314             $prefix // '' ne ''
315             ? "unknown prefix: $prefix" : "not an URI: $name");
316 63         203 $self->iri($base.$name);
317             }
318              
319             sub triple {
320 74     74 0 159 my $self = shift;
321 74 100       177 my $subject = ref $_[0] ? '_:'.${$_[0]} : $_[0];
  6         24  
322 74         136 my $predicate = $_[1];
323 74         89 my @object = @{$_[2]};
  74         174  
324 74 100       180 $object[0] = '_:'.${$object[0]} if ref $object[0];
  10         26  
325 74         264 $self->{callback}->($subject, $predicate, @object);
326             }
327              
328             sub error {
329 34     34 0 104 my ($self, $message, $value, $context) = @_;
330              
331             # TODO: include $context (bless $message, 'RDF::aREF::Error')
332              
333 34 100       94 if (defined $value) {
334 8 50       30 $message .= ': ' . (ref $value ? reftype $value : $value);
335             }
336            
337 34 50       112 if (!$self->{complain}) {
    100          
338 0         0 return;
339             } elsif ($self->{complain} == 1) {
340 1         37 carp $message;
341             } else {
342 33         4882 croak $message;
343             }
344             }
345              
346             sub bnode_count {
347 2 100   2 1 882 $_[0]->{bnode_count} = $_[1] if @_ > 1;
348 2         7 $_[0]->{bnode_count};
349             }
350              
351             # TODO: test this
352             sub blank_identifier {
353 13     13 0 44 my ($self, $id) = @_;
354              
355 13         25 my $bnode;
356 13 100       28 if ( defined $id ) {
357 8   66     107 $bnode = ($self->{bnode_map}{$id} //= $self->{bnode_prefix} . ++$self->{bnode_count});
358             } else {
359 5         16 $bnode = $self->{bnode_prefix} . ++$self->{bnode_count};
360             }
361              
362 13         42 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 297 do {
381 22 100       937 if (@_ == 3) {
382 15 100       49 if (substr($_[2],0,2) eq '_:') {
383 5         26 RDF::Trine::Node::Blank->new(substr $_[2], 2);
384             } else {
385 10         26 RDF::Trine::Node::Resource->new($_[2]);
386             }
387             } else {
388 7         40 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