File Coverage

blib/lib/RDF/aREF/Encoder.pm
Criterion Covered Total %
statement 119 123 96.7
branch 71 86 82.5
condition 23 32 71.8
subroutine 19 19 100.0
pod 10 11 90.9
total 242 271 89.3


line stmt bran cond sub pod time code
1             package RDF::aREF::Encoder;
2 8     8   154269 use strict;
  8         18  
  8         211  
3 8     8   36 use warnings;
  8         12  
  8         179  
4 8     8   80 use v5.10;
  8         22  
5              
6             our $VERSION = '0.27';
7              
8 8     8   450 use RDF::NS;
  8         24572  
  8         212  
9 8     8   449 use RDF::aREF::Decoder qw(localName blankNodeIdentifier);
  8         15  
  8         387  
10 8     8   45 use Scalar::Util qw(blessed reftype);
  8         14  
  8         365  
11 8     8   50 use Carp qw(croak);
  8         14  
  8         10007  
12              
13             sub new {
14 12     12 0 2192 my ($class, %options) = @_;
15              
16 12 100 33     83 if (!defined $options{ns}) {
    100          
    50          
17 3         17 $options{ns} = RDF::NS->new;
18             } elsif (!$options{ns}) {
19 3         20 $options{ns} = bless {
20             rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
21             rdfs => 'http://www.w3.org/2000/01/rdf-schema#',
22             owl => 'http://www.w3.org/2002/07/owl#',
23             xsd => 'http://www.w3.org/2001/XMLSchema#',
24             }, 'RDF::NS';
25             } elsif ( !blessed $options{ns} or !$options{ns}->isa('RDF::NS') ) {
26 6         45 $options{ns} = RDF::NS->new($options{ns});
27             }
28              
29 12         233803 $options{sn} = $options{ns}->REVERSE;
30 12         56677 $options{subject_map} = !!$options{subject_map};
31 12 100       51 if ($options{NFC}) {
32 1         3 eval { require Unicode::Normalize };
  1         670  
33 1 50       1796 croak "Missing Unicode::Normalize: NFC normalization disabled!\n" if $@;
34             }
35              
36 12         492 bless \%options, $class;
37             }
38              
39             sub qname {
40 35     35 1 2102 my ($self, $uri) = @_;
41 35 50       83 return unless $self->{sn};
42 35         112 my @qname = $self->{sn}->qname($uri);
43 35 50       7061 return $qname[0] if @qname == 1;
44 35 100 66     255 return join('_',@qname) if @qname and $qname[1] =~ localName;
45 10         28 return;
46             }
47              
48             sub uri {
49 17     17 1 1202 my ($self, $uri) = @_;
50              
51 17 100       36 if ( my $qname = $self->qname($uri) ) {
52 12         52 return $qname;
53             } else {
54 5         26 return "<$uri>";
55             }
56             }
57              
58             sub subject {
59 20     20 1 3353 my ($self, $subject) = @_;
60              
61 20         23 return do {
62 20 100       126 if (!reftype $subject) {
    100          
    50          
63             undef
64             # RDF/JSON
65 1         3 } elsif (reftype $subject eq 'HASH') {
66 2 50 66     12 if ($subject->{type} eq 'uri' or $subject->{type} eq 'bnode') {
67             $subject->{value}
68 2         10 }
69             # RDF::Trine::Node
70             } elsif (reftype $subject eq 'ARRAY') {
71 17 50       48 if (@$subject == 2 ) {
72 17 100       46 if ($subject->[0] eq 'URI') {
    50          
73 16         109 "".$subject->[1];
74             } elsif ($subject->[0] eq 'BLANK') {
75 1         4 $self->bnode($subject->[1])
76             }
77             }
78             }
79             };
80             }
81              
82             sub predicate {
83 22     22 1 2944 my ($self, $predicate) = @_;
84              
85 22         30 $predicate = do {
86 22 100 66     152 if (!reftype $predicate) {
    100          
    50          
87             undef
88             # RDF/JSON
89 2         5 } elsif (reftype $predicate eq 'HASH' and $predicate->{type} eq 'uri') {
90             $predicate->{value}
91             # RDF::Trine::Node
92 2         5 } elsif (reftype $predicate eq 'ARRAY') {
93 18 100 66     96 (@$predicate == 2 and $predicate->[0] eq 'URI')
94             ? "".$predicate->[1] : undef;
95             }
96             };
97              
98 22         31 return do {
99 22 100       76 if ( !defined $predicate ) {
    100          
    100          
100             undef
101 3         9 } elsif ( $predicate eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' ) {
102 6         20 'a'
103             } elsif ( my $qname = $self->qname($predicate) ) {
104 9         32 $qname
105             } else {
106 4         13 $predicate
107             }
108             };
109             }
110              
111             sub object {
112 28     28 1 6623 my ($self, $object) = @_;
113              
114 28         38 return do {
115 28 100       182 if (!reftype $object) {
    100          
    50          
116             undef
117             # RDF/JSON
118 1         4 } elsif (reftype $object eq 'HASH') {
119 10 100       28 if ($object->{type} eq 'literal') {
    100          
120             $self->literal( $object->{value}, $object->{lang}, $object->{datatype} )
121 5         22 } elsif ($object->{type} eq 'bnode') {
122             $object->{value}
123 1         3 } else {
124             $self->uri($object->{value})
125 4         12 }
126             # RDF::Trine::Node
127             } elsif (reftype $object eq 'ARRAY') {
128 17 100       51 if (@$object != 2 ) {
    100          
    50          
129 8         22 $self->literal(@$object)
130             } elsif ($object->[0] eq 'URI') {
131 8         32 $self->uri("".$object->[1])
132             } elsif ($object->[0] eq 'BLANK') {
133 1         4 $self->bnode($object->[1])
134             }
135             }
136             };
137             }
138              
139             sub literal {
140 16     16 1 1277 my ($self, $value, $language, $datatype) = @_;
141 16 100       41 if ($self->{NFC}) {
142 2         36 $value = Unicode::Normalize::NFC($value);
143             }
144 16 100 100     57 if ($language) {
    100          
145 6         28 $value.'@'.$language
146             } elsif ($datatype and $datatype ne 'http://www.w3.org/2001/XMLSchema#string') {
147 3         10 $value.'^'.$self->uri($datatype)
148             } else {
149 7         34 $value.'@'
150             }
151             }
152              
153             sub bnode {
154 5 100   5 1 1778 $_[1] =~ blankNodeIdentifier ? '_:'.$_[1] : undef;
155             }
156              
157             sub triple {
158 15     15 1 302 my ($self, $subject, $predicate, $object, $aref) = @_;
159            
160 15   50     42 $subject = $self->subject($subject) // return;
161 15   50     51 $predicate = $self->predicate($predicate) // return;
162 15   50     36 $object = $self->object($object) // return;
163 15   100     41 $aref //= { };
164              
165             # empty
166 15 100 100     74 if ( !keys %$aref and !$self->{subject_map} ) {
    100          
167 8         20 $aref->{_id} = $subject;
168 8         19 $aref->{$predicate} = $object;
169             # predicate map
170             } elsif ( $aref->{_id} ) {
171 6 100 100     30 if ( $aref->{_id} eq $subject and !$self->{subject_map} ) {
172 4         13 $self->_add_object_to_predicate_map( $aref, $predicate, $object );
173             } else {
174             # convert predicate map to subject map
175 2         5 my $s = delete $aref->{_id};
176 2         4 my $pm = { };
177 2         8 foreach (keys %$aref) {
178 3         18 $pm->{$_} = delete $aref->{$_};
179             }
180 2 100       10 if ($s eq $subject) {
181 1         4 $self->_add_object_to_predicate_map( $pm, $predicate, $object );
182             } else {
183 1         3 $aref->{$subject} = { $predicate => $object };
184             }
185 2         5 $aref->{$s} = $pm;
186             }
187             } else { # subject map
188 1 50       4 if ( $aref->{$subject} ) {
189 0         0 $self->_add_object_to_predicate_map( $aref->{$subject}, $predicate, $object );
190             } else {
191 1         3 $aref->{$subject} = { $predicate => $object };
192             }
193             }
194              
195 15         76 return $aref;
196             }
197              
198             sub _add_object_to_predicate_map {
199 5     5   16 my ($self, $map, $predicate, $object) = @_;
200              
201 5 50       24 if (ref $map->{$predicate}) {
    50          
202 0         0 push @{$map->{$predicate}}, $object;
  0         0  
203             } elsif (defined $map->{$predicate}) {
204 0         0 $map->{$predicate} = [ $map->{$predicate}, $object ];
205             } else {
206 5         15 $map->{$predicate} = $object;
207             }
208             }
209              
210             sub add_iterator {
211 4     4 1 5360 my ($self, $iterator, $aref) = @_;
212 4         14 while (my $s = $iterator->next) {
213 9         540 $self->triple($s->subject, $s->predicate, $s->object, $aref);
214             }
215             }
216            
217             sub add_hashref {
218 4     4 1 9 my ($self, $hashref, $aref) = @_;
219            
220 4         15 while (my ($s,$ps) = each %$hashref) {
221 4 50       17 my $subject = $s =~ /^_:/ ? ['BLANK',substr($s, 2)] : ['URI',$s];
222 4         12 foreach my $p (keys %$ps) {
223 4         9 my $predicate = ['URI',$p];
224 4         5 foreach my $object (@{ $hashref->{$s}->{$p} }) {
  4         10  
225 4         10 $self->triple($subject, $predicate, $object, $aref);
226             }
227             }
228             }
229            
230             }
231              
232             1;
233             __END__
234              
235             =head1 NAME
236              
237             RDF::aREF::Encoder - encode RDF to another RDF Encoding Form
238              
239             =head1 SYNOPSIS
240              
241             use RDF::aREF::Encoder;
242             my $encoder = RDF::aREF::Encoder->new;
243            
244             # encode parts of aREF
245              
246             my $qname = $encoder->qname('http://schema.org/Review'); # 'schema_Review'
247              
248              
249             my $predicate = $encoder->predicate({
250             type => 'uri',
251             value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
252             }); # 'a'
253              
254             my $object = $encoder->object({
255             type => 'literal',
256             value => 'hello, world!',
257             lang => 'en'
258             }); # 'hello, world!@en'
259              
260             # method also accepts RDF::Trine::Node instances
261             my $object = $encoder->object( RDF::Trine::Resource->new($iri) );
262              
263             # encode RDF graphs (see also function 'encode_aref' in RDF::aREF)
264             use RDF::Trine::Parser;
265             my $aref = { };
266             RDF::Trine::Parser->parse_file ( $base_uri, $fh, sub {
267             my $s = shift;
268             $encoder->triple( $s->subject, $s->predicate, $s->object, $aref );
269             } );
270              
271             =head1 DESCRIPTION
272              
273             This module provides methods to encode RDF data in another RDF Encoding Form
274             (aREF). As aREF was designed to facilitate creation of RDF data, it may be
275             easier to create aREF "by hand" instead of using this module!
276              
277             =head1 OPTIONS
278              
279             =head2 ns
280              
281             A default namespace map, given as version string of module L<RDF::NS> for
282             stable qNames or as instance of L<RDF::NS>. The most recent installed version
283             of L<RDF::NS> is used by default. The value C<0> can be used to only use
284             required namespace mappings (rdf, rdfs, owl and xsd).
285              
286             =head2 subject_map
287              
288             By default RDF graphs with common subject are encoded as aREF predicate map:
289              
290             {
291             _id => $subject, $predicate => $object
292             }
293              
294             Enable this option to always encode as aREF subject map:
295              
296             {
297             $subject => { $predicate => $object }
298             }
299              
300             =head1 METHODS
301              
302             Note that no syntax checking is applied, e.g. whether a given URI is a valid
303             URI or whether a given language is a valid language tag!
304              
305             =head2 qname( $uri )
306              
307             Abbreviate an URI as qName or return C<undef>. For instance
308             C<http://purl.org/dc/terms/title> is abbreviated to "C<dct_title>".
309              
310             =head2 uri( $uri )
311              
312             Abbreviate an URI or as qName or enclose it in angular brackets.
313              
314             =head2 literal( $value, $language_tag, $datatype_uri )
315              
316             Encode a literal RDF node by either appending "C<@>" and an optional
317             language tag, or "C<^>" and an datatype URI.
318              
319             =head2 bnode( $identifier )
320              
321             Encode a blank node by prepending "C<_:>" to its identifier.
322              
323             =head2 subject( $subject )
324              
325             =head2 predicate( $predicate )
326              
327             =head2 object( $object )
328              
329             Encode an RDF subject, predicate, or object respectively. The argument must
330             either be given as hash reference, as defined in
331             L<RDF/JSON|http://www.w3.org/TR/rdf-json/> format (see also method
332             C<as_hashref> of L<RDF::Trine::Model>), or as array reference as internally
333             used by L<RDF::Trine>.
334              
335             A hash reference is expected to have the following fields:
336              
337             =over
338              
339             =item type
340              
341             one of C<uri>, C<literal> or C<bnode> (required)
342              
343             =item value
344              
345             the URI of the object, its lexical value or a blank node label depending on
346             whether the object is a uri, literal or bnode
347              
348             =item lang
349              
350             the language of a literal value (optional but if supplied it must not be empty)
351              
352             =item datatype
353              
354             the datatype URI of the literal value (optional)
355              
356             =back
357              
358             An array reference is expected to consists of
359              
360             =over
361              
362             =item
363              
364             three elements (value, language tag, and datatype uri) for literal nodes,
365              
366             =item
367              
368             two elements "C<URI>" and the URI for URI nodes,
369              
370             =item
371              
372             two elements "C<BLANK>" and the blank node identifier for blank nodes.
373              
374             =back
375              
376             =head2 triple( $subject, $predicate, $object, [, $aref ] )
377              
378             Encode an RDF triple, its elements given as explained for method C<subject>,
379             C<predicate>, and C<object>. If an aREF data structure is given as fourth
380             argument, the triple is added to this structure, possibly changing an aREF
381             predicate map to an aRef subject map. Returns C<undef> on failure.
382              
383             =head2 add_hashref( $aref, $rdf )
384            
385             Add RDF given in L<RDF/JSON|http://www.w3.org/TR/rdf-json/> format (as returned
386             by method C<as_hashref> in L<RDF::Trine::Model>).
387              
388             =head2 add_iterator( $aref, $iterator )
389            
390             Add a L<RDF::Trine::Iterator> to an aREF subject map.
391            
392             I<experimental>
393              
394             =head1 SEE ALSO
395              
396             L<RDF::aREF::Decoder>, L<RDF::Trine::Node>
397              
398             =cut